From 6bdc498d423ca793b3db2d158fba4a461610de6a Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 14 Oct 2023 16:04:35 +0100 Subject: [PATCH 001/173] Migrate Python package metadata to pyproject.toml --- MANIFEST.in | 6 +++++- pyproject.toml | 58 ++++++++++++++++++++++++++++++++++++++++++++++---- setup.cfg | 40 ---------------------------------- setup.py | 3 --- 4 files changed, 59 insertions(+), 48 deletions(-) delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/MANIFEST.in b/MANIFEST.in index 64ad321d..10dd5123 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1 +1,5 @@ -include README.md LICENSE +include README.md LICENSE tox.ini +recursive-include docs * +recursive-exclude docs/_build * +recursive-include tests * +exclude **/*.pyc diff --git a/pyproject.toml b/pyproject.toml index 374b58cb..1f82bc5d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,56 @@ -[build-system] -requires = [ - "setuptools>=42", - "wheel" +[project] +name = "python-socketio" +version = "5.9.1.dev0" +authors = [ + { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, +] +description = "Socket.IO server and client for Python" +classifiers = [ + "Environment :: Web Environment", + "Intended Audience :: Developers", + "Programming Language :: Python :: 3", + "License :: OSI Approved :: MIT License", + "Operating System :: OS Independent", +] +requires-python = ">=3.6" +dependencies = [ + "bidict >= 0.21.0", + "python-engineio >= 4.8.0", +] + +[project.readme] +file = "README.md" +content-type = "text/markdown" + +[project.urls] +Homepage = "https://github.com/miguelgrinberg/python-socketio" +"Bug Tracker" = "https://github.com/miguelgrinberg/python-socketio/issues" + +[project.optional-dependencies] +client = [ + "requests >= 2.21.0", + "websocket-client >= 0.54.0", ] +asyncio_client = [ + "aiohttp >= 3.4", +] +docs = [ + "sphinx", +] + +[tool.setuptools] +zip-safe = false +include-package-data = true + +[tool.setuptools.package-dir] +"" = "src" + +[tool.setuptools.packages.find] +where = [ + "src", +] +namespaces = false + +[build-system] +requires = ["setuptools>=61.2"] build-backend = "setuptools.build_meta" diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index e5e80a6c..00000000 --- a/setup.cfg +++ /dev/null @@ -1,40 +0,0 @@ -[metadata] -name = python-socketio -version = 5.9.1.dev0 -author = Miguel Grinberg -author_email = miguel.grinberg@gmail.com -description = Socket.IO server and client for Python -long_description = file: README.md -long_description_content_type = text/markdown -url = https://github.com/miguelgrinberg/python-socketio -project_urls = - Bug Tracker = https://github.com/miguelgrinberg/python-socketio/issues -classifiers = - Environment :: Web Environment - Intended Audience :: Developers - Programming Language :: Python :: 3 - License :: OSI Approved :: MIT License - Operating System :: OS Independent - -[options] -zip_safe = False -include_package_data = True -package_dir = - = src -packages = find: -python_requires = >=3.6 -install_requires = - bidict >= 0.21.0 - python-engineio >= 4.7.0 - -[options.packages.find] -where = src - -[options.extras_require] -client = - requests >= 2.21.0 - websocket-client >= 0.54.0 -asyncio_client = - aiohttp >= 3.4 -docs = - sphinx diff --git a/setup.py b/setup.py deleted file mode 100644 index b908cbe5..00000000 --- a/setup.py +++ /dev/null @@ -1,3 +0,0 @@ -import setuptools - -setuptools.setup() From c85c7d8a246ab13592f9e5750080e454c20c65c3 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 15 Oct 2023 00:43:09 +0100 Subject: [PATCH 002/173] Update readme #nolog --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7c453c21..780465f4 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ python-socketio [![Build status](https://github.com/miguelgrinberg/python-socketio/workflows/build/badge.svg)](https://github.com/miguelgrinberg/python-socketio/actions) [![codecov](https://codecov.io/gh/miguelgrinberg/python-socketio/branch/main/graph/badge.svg)](https://codecov.io/gh/miguelgrinberg/python-socketio) -Python implementation of the `Socket.IO`_ realtime client and server. +Python implementation of the `Socket.IO` realtime client and server. Sponsors -------- @@ -40,7 +40,7 @@ JavaScript Socket.IO version | Socket.IO protocol revision | Engine.IO protocol Resources --------- -- [Documentation](http://python-socketio.readthedocs.io/en/latest/) +- [Documentation](http://python-socketio.readthedocs.io/) - [PyPI](https://pypi.python.org/pypi/python-socketio) - [Change Log](https://github.com/miguelgrinberg/python-socketio/blob/main/CHANGES.md) - Questions? See the [questions](https://stackoverflow.com/questions/tagged/python-socketio) others have asked on Stack Overflow, or [ask](https://stackoverflow.com/questions/ask?tags=python+python-socketio) your own question. From 4bf48776ca08acba255bf660f5ca61810202826a Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 15 Oct 2023 12:34:49 +0100 Subject: [PATCH 003/173] Reporting to Socket.IO Admin UI (#1164) --- .github/workflows/tests.yml | 2 +- docs/server.rst | 35 ++ examples/server/asgi/app.py | 20 +- examples/server/wsgi/app.py | 18 +- examples/server/wsgi/templates/index.html | 2 +- src/socketio/admin.py | 405 ++++++++++++++++++++++ src/socketio/async_admin.py | 398 +++++++++++++++++++++ src/socketio/async_manager.py | 7 + src/socketio/async_server.py | 41 ++- src/socketio/async_simple_client.py | 10 +- src/socketio/base_manager.py | 4 +- src/socketio/server.py | 39 +++ src/socketio/simple_client.py | 10 +- tests/async/test_admin.py | 311 +++++++++++++++++ tests/async/test_manager.py | 116 +++---- tests/async/test_pubsub_manager.py | 14 +- tests/async/test_server.py | 24 +- tests/async/test_simple_client.py | 12 +- tests/asyncio_web_server.py | 57 +++ tests/common/test_admin.py | 286 +++++++++++++++ tests/common/test_simple_client.py | 12 +- tests/web_server.py | 81 +++++ tox.ini | 8 +- 23 files changed, 1810 insertions(+), 102 deletions(-) create mode 100644 src/socketio/admin.py create mode 100644 src/socketio/async_admin.py create mode 100644 tests/async/test_admin.py create mode 100644 tests/asyncio_web_server.py create mode 100644 tests/common/test_admin.py create mode 100644 tests/web_server.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index af896fb4..ddc190c8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: exclude: # pypy3 currently fails to run on Windows - os: windows-latest - python: pypy-3.8 + python: pypy-3.9 fail-fast: false runs-on: ${{ matrix.os }} steps: diff --git a/docs/server.rst b/docs/server.rst index 5a6798c2..5ce59f00 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -617,6 +617,41 @@ callbacks when emitting. When the external process needs to receive callbacks, using a client to connect to the server with read and write support is a better option than a write-only client manager. +Monitoring and Administration +----------------------------- + +The Socket.IO server can be configured to accept connections from the official +`Socket.IO Admin UI `_. This tool provides +real-time information about currently connected clients, rooms in use and +events being emitted. It also allows an administrator to manually emit events, +change room assignments and disconnect clients. The hosted version of this tool +is available at `https://admin.socket.io `_. + +Given that enabling this feature can affect the performance of the server, it +is disabled by default. To enable it, call the +:func:`instrument() ` method. For example:: + + import os + import socketio + + sio = socketio.Server(cors_allowed_origins=[ + 'http://localhost:5000', + 'https://admin.socket.io', + ]) + sio.instrument(auth={ + 'username': 'admin', + 'password': os.environ['ADMIN_PASSWORD'], + }) + +This configures the server to accept connections from the hosted Admin UI +client. Administrators can then open https://admin.socket.io in their web +browsers and log in with username ``admin`` and the password given by the +``ADMIN_PASSWORD`` environment variable. To ensure the Admin UI front end is +allowed to connect, CORS is also configured. + +Consult the reference documentation to learn about additional configuration +options that are available. + Debugging and Troubleshooting ----------------------------- diff --git a/examples/server/asgi/app.py b/examples/server/asgi/app.py index 22180bb0..36af85f2 100644 --- a/examples/server/asgi/app.py +++ b/examples/server/asgi/app.py @@ -1,9 +1,25 @@ #!/usr/bin/env python -import uvicorn +# set instrument to `True` to accept connections from the official Socket.IO +# Admin UI hosted at https://admin.socket.io +instrument = False +admin_login = { + 'username': 'admin', + 'password': 'python', # change this to a strong secret for production use! +} + +import uvicorn import socketio -sio = socketio.AsyncServer(async_mode='asgi') +sio = socketio.AsyncServer( + async_mode='asgi', + cors_allowed_origins=None if not instrument else [ + 'http://localhost:5000', + 'https://admin.socket.io', # edit the allowed origins if necessary + ]) +if instrument: + sio.instrument(auth=admin_login) + app = socketio.ASGIApp(sio, static_files={ '/': 'app.html', }) diff --git a/examples/server/wsgi/app.py b/examples/server/wsgi/app.py index 3339826a..7b019fd0 100644 --- a/examples/server/wsgi/app.py +++ b/examples/server/wsgi/app.py @@ -3,10 +3,26 @@ # installed async_mode = None +# set instrument to `True` to accept connections from the official Socket.IO +# Admin UI hosted at https://admin.socket.io +instrument = False +admin_login = { + 'username': 'admin', + 'password': 'python', # change this to a strong secret for production use! +} + from flask import Flask, render_template import socketio -sio = socketio.Server(logger=True, async_mode=async_mode) +sio = socketio.Server( + async_mode=async_mode, + cors_allowed_origins=None if not instrument else [ + 'http://localhost:5000', + 'https://admin.socket.io', # edit the allowed origins if necessary + ]) +if instrument: + sio.instrument(auth=admin_login) + app = Flask(__name__) app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app) app.config['SECRET_KEY'] = 'secret!' diff --git a/examples/server/wsgi/templates/index.html b/examples/server/wsgi/templates/index.html index 7c9ae41f..bec1a628 100644 --- a/examples/server/wsgi/templates/index.html +++ b/examples/server/wsgi/templates/index.html @@ -6,7 +6,7 @@ -

Flask-SocketIO Test

+

Python-SocketIO Test

Send:

diff --git a/examples/server/wsgi/templates/index.html b/examples/server/wsgi/templates/index.html index bec1a628..8a7308af 100644 --- a/examples/server/wsgi/templates/index.html +++ b/examples/server/wsgi/templates/index.html @@ -1,7 +1,7 @@ - Flask-SocketIO Test + Python-SocketIO Test -

Flask-SocketIO Test

+

Python-SocketIO Test

Send:

diff --git a/src/socketio/kombu_manager.py b/src/socketio/kombu_manager.py index 0a63bc26..09e260c9 100644 --- a/src/socketio/kombu_manager.py +++ b/src/socketio/kombu_manager.py @@ -86,7 +86,7 @@ def _exchange(self): return kombu.Exchange(self.channel, **options) def _queue(self): - queue_name = 'flask-socketio.' + str(uuid.uuid4()) + queue_name = 'python-socketio.' + str(uuid.uuid4()) options = {'durable': False, 'queue_arguments': {'x-expires': 300000}} options.update(self.queue_options) return kombu.Queue(queue_name, self._exchange(), **options) From c535558a8e2e651f38023d1c570739adee2c6dc6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 10:59:36 +0100 Subject: [PATCH 065/173] Bump path-to-regexp and express in /examples/client/javascript (#1381) #nolog Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) to 0.1.10 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `path-to-regexp` from 0.1.7 to 0.1.10 - [Release notes](https://github.com/pillarjs/path-to-regexp/releases) - [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md) - [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.7...v0.1.10) Updates `express` from 4.19.2 to 4.20.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0) --- updated-dependencies: - dependency-name: path-to-regexp dependency-type: indirect - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/client/javascript/package-lock.json | 214 ++++++++++++++----- examples/client/javascript/package.json | 2 +- 2 files changed, 158 insertions(+), 58 deletions(-) diff --git a/examples/client/javascript/package-lock.json b/examples/client/javascript/package-lock.json index d88717a4..6c7f856a 100644 --- a/examples/client/javascript/package-lock.json +++ b/examples/client/javascript/package-lock.json @@ -8,7 +8,7 @@ "name": "socketio-examples", "version": "0.1.0", "dependencies": { - "express": "^4.19.2", + "express": "^4.20.0", "smoothie": "1.19.0", "socket.io": "^4.6.1", "socket.io-client": "^4.6.1" @@ -63,9 +63,9 @@ } }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -75,7 +75,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -85,6 +85,20 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -332,36 +346,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", + "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.2.0", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -372,6 +386,14 @@ "node": ">= 0.10.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -534,9 +556,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/methods": { "version": "1.1.2", @@ -598,9 +623,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -625,9 +653,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -702,9 +730,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -730,9 +758,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", + "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -743,6 +771,34 @@ "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -1047,9 +1103,9 @@ "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -1059,10 +1115,20 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" + }, + "dependencies": { + "qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "requires": { + "side-channel": "^1.0.6" + } + } } }, "bytes": { @@ -1245,41 +1311,48 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", + "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.2.0", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" + }, + "dependencies": { + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + } } }, "finalhandler": { @@ -1393,9 +1466,9 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "methods": { "version": "1.1.2", @@ -1436,9 +1509,9 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" }, "on-finished": { "version": "2.4.1", @@ -1454,9 +1527,9 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "proxy-addr": { "version": "2.0.7", @@ -1502,9 +1575,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { "debug": "2.6.9", "depd": "2.0.0", @@ -1529,14 +1602,41 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", + "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.18.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + } + } } }, "set-function-length": { diff --git a/examples/client/javascript/package.json b/examples/client/javascript/package.json index 2de1e136..3a173a86 100644 --- a/examples/client/javascript/package.json +++ b/examples/client/javascript/package.json @@ -2,7 +2,7 @@ "name": "socketio-examples", "version": "0.1.0", "dependencies": { - "express": "^4.19.2", + "express": "^4.20.0", "smoothie": "1.19.0", "socket.io": "^4.6.1", "socket.io-client": "^4.6.1" From 3157681c1a30ec37d6ee71261d805a2893c1e16a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:46:37 +0100 Subject: [PATCH 066/173] Bump send and express in /examples/server/javascript (#1385) #nolog Bumps [send](https://github.com/pillarjs/send) to 0.19.0 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `send` from 0.18.0 to 0.19.0 - [Release notes](https://github.com/pillarjs/send/releases) - [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md) - [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0) Updates `express` from 4.19.2 to 4.20.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0) --- updated-dependencies: - dependency-name: send dependency-type: indirect - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/javascript/package-lock.json | 238 ++++++++++++++----- examples/server/javascript/package.json | 2 +- 2 files changed, 182 insertions(+), 58 deletions(-) diff --git a/examples/server/javascript/package-lock.json b/examples/server/javascript/package-lock.json index 44349e94..8211e39b 100644 --- a/examples/server/javascript/package-lock.json +++ b/examples/server/javascript/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@socket.io/admin-ui": "^0.5.1", - "express": "^4.19.2", + "express": "^4.20.0", "smoothie": "1.19.0", "socket.io": "^4.6.1", "socket.io-client": "^4.6.1" @@ -87,9 +87,9 @@ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -99,7 +99,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -122,6 +122,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -335,36 +349,36 @@ } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", + "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.2.0", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -383,6 +397,14 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -563,9 +585,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/methods": { "version": "1.1.2", @@ -627,9 +652,12 @@ } }, "node_modules/object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -654,9 +682,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -731,9 +759,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "dependencies": { "debug": "2.6.9", "depd": "2.0.0", @@ -772,9 +800,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", + "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", "dependencies": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", @@ -785,6 +813,47 @@ "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-static/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -1025,9 +1094,9 @@ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==" }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -1037,7 +1106,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -1055,6 +1124,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "requires": { + "side-channel": "^1.0.6" + } } } }, @@ -1210,36 +1287,36 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", + "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.6.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.2.0", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", "qs": "6.11.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -1255,6 +1332,11 @@ "ms": "2.0.0" } }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1388,9 +1470,9 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "methods": { "version": "1.1.2", @@ -1431,9 +1513,9 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", - "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" }, "on-finished": { "version": "2.4.1", @@ -1449,9 +1531,9 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "proxy-addr": { "version": "2.0.7", @@ -1497,9 +1579,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", "requires": { "debug": "2.6.9", "depd": "2.0.0", @@ -1539,14 +1621,56 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", + "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", "requires": { "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "parseurl": "~1.3.3", "send": "0.18.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + } + } } }, "set-function-length": { diff --git a/examples/server/javascript/package.json b/examples/server/javascript/package.json index 94aa21d7..04d6abad 100644 --- a/examples/server/javascript/package.json +++ b/examples/server/javascript/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "dependencies": { "@socket.io/admin-ui": "^0.5.1", - "express": "^4.19.2", + "express": "^4.20.0", "smoothie": "1.19.0", "socket.io": "^4.6.1", "socket.io-client": "^4.6.1" From 98f94f465bd7217398a88dc0f0d37bb7ad9602a1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:46:56 +0100 Subject: [PATCH 067/173] Bump serve-static and express in /examples/server/javascript (#1386) #nolog Bumps [serve-static](https://github.com/expressjs/serve-static) to 1.16.0 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `serve-static` from 1.15.0 to 1.16.0 - [Release notes](https://github.com/expressjs/serve-static/releases) - [Changelog](https://github.com/expressjs/serve-static/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/serve-static/compare/v1.15.0...1.16.0) Updates `express` from 4.19.2 to 4.20.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0) --- updated-dependencies: - dependency-name: serve-static dependency-type: indirect - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From aaab78a47923c8b645ee2f116494a24b7033c7e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:47:14 +0100 Subject: [PATCH 068/173] Bump path-to-regexp and express in /examples/server/javascript (#1383) #nolog Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) to 0.1.10 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `path-to-regexp` from 0.1.7 to 0.1.10 - [Release notes](https://github.com/pillarjs/path-to-regexp/releases) - [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md) - [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.7...v0.1.10) Updates `express` from 4.19.2 to 4.20.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0) --- updated-dependencies: - dependency-name: path-to-regexp dependency-type: indirect - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From 664acee9ef081ceef98d9ff2a292ac3233f6d210 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 11:47:31 +0100 Subject: [PATCH 069/173] Bump body-parser and express in /examples/server/javascript (#1384) #nolog Bumps [body-parser](https://github.com/expressjs/body-parser) to 1.20.3 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `body-parser` from 1.20.2 to 1.20.3 - [Release notes](https://github.com/expressjs/body-parser/releases) - [Changelog](https://github.com/expressjs/body-parser/blob/master/HISTORY.md) - [Commits](https://github.com/expressjs/body-parser/compare/1.20.2...1.20.3) Updates `express` from 4.19.2 to 4.20.0 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/master/History.md) - [Commits](https://github.com/expressjs/express/compare/4.19.2...4.20.0) --- updated-dependencies: - dependency-name: body-parser dependency-type: indirect - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> From a9b02a5a65d8e72f3a812037afd361b06aaaa040 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 23:00:16 +0100 Subject: [PATCH 070/173] Bump django in /examples/server/wsgi/django_socketio (#1392) #nolog Bumps [django](https://github.com/django/django) from 4.2.15 to 4.2.16. - [Commits](https://github.com/django/django/compare/4.2.15...4.2.16) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/django_socketio/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/django_socketio/requirements.txt b/examples/server/wsgi/django_socketio/requirements.txt index a247dbf8..53a28118 100644 --- a/examples/server/wsgi/django_socketio/requirements.txt +++ b/examples/server/wsgi/django_socketio/requirements.txt @@ -1,6 +1,6 @@ asgiref==3.6.0 bidict==0.22.1 -Django==4.2.15 +Django==4.2.16 gunicorn==22.0.0 h11==0.14.0 python-engineio From 694d0a294ce761f7ae568025fc93b224ca98711a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 23:03:00 +0100 Subject: [PATCH 071/173] Bump send and express in /examples/client/javascript (#1393) #nolog Bumps [send](https://github.com/pillarjs/send) to 0.19.0 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `send` from 0.18.0 to 0.19.0 - [Release notes](https://github.com/pillarjs/send/releases) - [Changelog](https://github.com/pillarjs/send/blob/master/HISTORY.md) - [Commits](https://github.com/pillarjs/send/compare/0.18.0...0.19.0) Updates `express` from 4.20.0 to 4.21.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.20.0...4.21.1) --- updated-dependencies: - dependency-name: send dependency-type: indirect - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/client/javascript/package-lock.json | 191 +++++++------------ examples/client/javascript/package.json | 2 +- 2 files changed, 72 insertions(+), 121 deletions(-) diff --git a/examples/client/javascript/package-lock.json b/examples/client/javascript/package-lock.json index 6c7f856a..9b87e499 100644 --- a/examples/client/javascript/package-lock.json +++ b/examples/client/javascript/package-lock.json @@ -8,7 +8,7 @@ "name": "socketio-examples", "version": "0.1.0", "dependencies": { - "express": "^4.20.0", + "express": "^4.21.1", "smoothie": "1.19.0", "socket.io": "^4.6.1", "socket.io-client": "^4.6.1" @@ -85,20 +85,6 @@ "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -145,9 +131,9 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -346,23 +332,23 @@ } }, "node_modules/express": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", - "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", @@ -371,11 +357,11 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", - "serve-static": "1.16.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -395,12 +381,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -411,6 +397,14 @@ "node": ">= 0.8" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -670,11 +664,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -758,45 +752,25 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", - "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8" } }, "node_modules/set-function-length": { @@ -1119,16 +1093,6 @@ "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" - }, - "dependencies": { - "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "requires": { - "side-channel": "^1.0.6" - } - } } }, "bytes": { @@ -1162,9 +1126,9 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" }, "cookie-signature": { "version": "1.0.6", @@ -1311,23 +1275,23 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", - "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", @@ -1336,11 +1300,11 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", - "serve-static": "1.16.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -1356,17 +1320,24 @@ } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" + }, + "dependencies": { + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + } } }, "forwarded": { @@ -1541,11 +1512,11 @@ } }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "range-parser": { @@ -1602,40 +1573,20 @@ } }, "serve-static": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", - "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "dependencies": { - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - } + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" } } }, diff --git a/examples/client/javascript/package.json b/examples/client/javascript/package.json index 3a173a86..a9bc3402 100644 --- a/examples/client/javascript/package.json +++ b/examples/client/javascript/package.json @@ -2,7 +2,7 @@ "name": "socketio-examples", "version": "0.1.0", "dependencies": { - "express": "^4.20.0", + "express": "^4.21.1", "smoothie": "1.19.0", "socket.io": "^4.6.1", "socket.io-client": "^4.6.1" From 42da5d2f5426e812fd37d4cabcb9277810cae9c1 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Thu, 10 Oct 2024 23:59:41 +0100 Subject: [PATCH 072/173] Add Python 3.13 CI builds --- .github/workflows/tests.yml | 2 +- tox.ini | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index efb0b628..9d2f4759 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [windows-latest, macos-latest, ubuntu-latest] - python: ['pypy-3.10', '3.8', '3.9', '3.10', '3.11', '3.12'] + python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10'] exclude: # pypy3 currently fails to run on Windows - os: windows-latest diff --git a/tox.ini b/tox.ini index 12deda1c..6d809d40 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=flake8,py{38,39,310,311,312,py3},docs +envlist=flake8,py{38,39,310,311,312,313},docs skip_missing_interpreters=True [gh-actions] @@ -9,6 +9,7 @@ python = 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 pypy-3: pypy3 [testenv] From e2237077de025e8bff6772ba1c56906ad03695e1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Oct 2024 15:12:07 +0100 Subject: [PATCH 073/173] Bump cookie, socket.io and express in /examples/server/javascript (#1395) #nolog Bumps [cookie](https://github.com/jshttp/cookie) to 0.7.1 and updates ancestor dependencies [cookie](https://github.com/jshttp/cookie), [socket.io](https://github.com/socketio/socket.io) and [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `cookie` from 0.6.0 to 0.7.1 - [Release notes](https://github.com/jshttp/cookie/releases) - [Commits](https://github.com/jshttp/cookie/compare/v0.6.0...v0.7.1) Updates `socket.io` from 4.7.2 to 4.8.0 - [Release notes](https://github.com/socketio/socket.io/releases) - [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/socket.io/compare/4.7.2...socket.io@4.8.0) Updates `express` from 4.20.0 to 4.21.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.20.0...4.21.1) --- updated-dependencies: - dependency-name: cookie dependency-type: indirect - dependency-name: socket.io dependency-type: direct:production - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/javascript/package-lock.json | 299 ++++++++----------- examples/server/javascript/package.json | 4 +- 2 files changed, 121 insertions(+), 182 deletions(-) diff --git a/examples/server/javascript/package-lock.json b/examples/server/javascript/package-lock.json index 8211e39b..befb6751 100644 --- a/examples/server/javascript/package-lock.json +++ b/examples/server/javascript/package-lock.json @@ -9,9 +9,9 @@ "version": "0.1.0", "dependencies": { "@socket.io/admin-ui": "^0.5.1", - "express": "^4.20.0", + "express": "^4.21.1", "smoothie": "1.19.0", - "socket.io": "^4.6.1", + "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" } }, @@ -44,17 +44,20 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, "node_modules/@types/cors": { - "version": "2.8.14", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", - "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/node": { - "version": "20.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", - "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==" + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dependencies": { + "undici-types": "~6.19.2" + } }, "node_modules/accepts": { "version": "1.3.8", @@ -122,20 +125,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/body-parser/node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -182,9 +171,9 @@ } }, "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { "node": ">= 0.6" } @@ -269,16 +258,16 @@ } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -309,9 +298,9 @@ } }, "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "engines": { "node": ">= 0.6" } @@ -349,23 +338,23 @@ } }, "node_modules/express": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", - "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", @@ -374,11 +363,11 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", - "serve-static": "1.16.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -411,12 +400,12 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -435,6 +424,14 @@ "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -699,11 +696,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -800,58 +797,25 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/serve-static": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", - "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "engines": { "node": ">= 0.8.0" } }, - "node_modules/serve-static/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "node_modules/serve-static/node_modules/encodeurl": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/serve-static/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node_modules/serve-static/node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - }, + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "engines": { - "node": ">= 0.8.0" + "node": ">= 0.8" } }, "node_modules/set-function-length": { @@ -898,15 +862,15 @@ "integrity": "sha512-DHH09adx8ltbo/8udr52RcOXggH7HTe0dPmFvTx9iShBl8QAr/WHogup4pU4hCEFWswus8cwNcP7KhTpH5ftCw==" }, "node_modules/socket.io": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", - "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -977,6 +941,11 @@ "node": ">= 0.6" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1057,17 +1026,20 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, "@types/cors": { - "version": "2.8.14", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", - "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "requires": { "@types/node": "*" } }, "@types/node": { - "version": "20.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", - "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==" + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "requires": { + "undici-types": "~6.19.2" + } }, "accepts": { "version": "1.3.8", @@ -1124,14 +1096,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "requires": { - "side-channel": "^1.0.6" - } } } }, @@ -1166,9 +1130,9 @@ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" }, "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" }, "cookie-signature": { "version": "1.0.6", @@ -1223,16 +1187,16 @@ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "requires": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -1240,9 +1204,9 @@ }, "dependencies": { "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" } } }, @@ -1287,23 +1251,23 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.20.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.20.0.tgz", - "integrity": "sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", @@ -1312,11 +1276,11 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", - "serve-static": "1.16.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -1345,12 +1309,12 @@ } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -1366,6 +1330,11 @@ "ms": "2.0.0" } }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -1545,11 +1514,11 @@ } }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "range-parser": { @@ -1621,55 +1590,20 @@ } }, "serve-static": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.0.tgz", - "integrity": "sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", - "requires": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" - } + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" } } }, @@ -1708,15 +1642,15 @@ "integrity": "sha512-DHH09adx8ltbo/8udr52RcOXggH7HTe0dPmFvTx9iShBl8QAr/WHogup4pU4hCEFWswus8cwNcP7KhTpH5ftCw==" }, "socket.io": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", - "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "requires": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" } @@ -1769,6 +1703,11 @@ "mime-types": "~2.1.24" } }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/examples/server/javascript/package.json b/examples/server/javascript/package.json index 04d6abad..b93cdb63 100644 --- a/examples/server/javascript/package.json +++ b/examples/server/javascript/package.json @@ -3,9 +3,9 @@ "version": "0.1.0", "dependencies": { "@socket.io/admin-ui": "^0.5.1", - "express": "^4.20.0", + "express": "^4.21.1", "smoothie": "1.19.0", - "socket.io": "^4.6.1", + "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" } } From d2059c1b8f628aef5931f6a5218f26a7ac0369cf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 13 Oct 2024 15:12:25 +0100 Subject: [PATCH 074/173] Bump cookie and socket.io in /examples/client/javascript (#1394) #nolog Bumps [cookie](https://github.com/jshttp/cookie) to 0.7.1 and updates ancestor dependency [socket.io](https://github.com/socketio/socket.io). These dependencies need to be updated together. Updates `cookie` from 0.4.2 to 0.7.1 - [Release notes](https://github.com/jshttp/cookie/releases) - [Commits](https://github.com/jshttp/cookie/compare/v0.4.2...v0.7.1) Updates `socket.io` from 4.7.2 to 4.8.0 - [Release notes](https://github.com/socketio/socket.io/releases) - [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md) - [Commits](https://github.com/socketio/socket.io/compare/4.7.2...socket.io@4.8.0) --- updated-dependencies: - dependency-name: cookie dependency-type: indirect - dependency-name: socket.io dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/client/javascript/package-lock.json | 114 +++++++++++-------- examples/client/javascript/package.json | 2 +- 2 files changed, 66 insertions(+), 50 deletions(-) diff --git a/examples/client/javascript/package-lock.json b/examples/client/javascript/package-lock.json index 9b87e499..ca02f7af 100644 --- a/examples/client/javascript/package-lock.json +++ b/examples/client/javascript/package-lock.json @@ -10,7 +10,7 @@ "dependencies": { "express": "^4.21.1", "smoothie": "1.19.0", - "socket.io": "^4.6.1", + "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" } }, @@ -25,17 +25,20 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, "node_modules/@types/cors": { - "version": "2.8.14", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", - "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "dependencies": { "@types/node": "*" } }, "node_modules/@types/node": { - "version": "20.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", - "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==" + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "dependencies": { + "undici-types": "~6.19.2" + } }, "node_modules/accepts": { "version": "1.3.8", @@ -210,16 +213,16 @@ } }, "node_modules/engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -271,19 +274,19 @@ } }, "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "engines": { "node": ">= 0.6" } }, "node_modules/engine.io/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -295,9 +298,9 @@ } }, "node_modules/engine.io/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/es-define-property": { "version": "1.0.0", @@ -817,15 +820,15 @@ "integrity": "sha512-DHH09adx8ltbo/8udr52RcOXggH7HTe0dPmFvTx9iShBl8QAr/WHogup4pU4hCEFWswus8cwNcP7KhTpH5ftCw==" }, "node_modules/socket.io": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", - "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "dependencies": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -980,6 +983,11 @@ "node": ">= 0.6" } }, + "node_modules/undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -1045,17 +1053,20 @@ "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==" }, "@types/cors": { - "version": "2.8.14", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.14.tgz", - "integrity": "sha512-RXHUvNWYICtbP6s18PnOCaqToK8y14DnLd75c6HfyKf228dxy7pHNOQkxPtvXKp/hINFMDjbYzsj63nnpPMSRQ==", + "version": "2.8.17", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.17.tgz", + "integrity": "sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA==", "requires": { "@types/node": "*" } }, "@types/node": { - "version": "20.6.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz", - "integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==" + "version": "22.7.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", + "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", + "requires": { + "undici-types": "~6.19.2" + } }, "accepts": { "version": "1.3.8", @@ -1183,16 +1194,16 @@ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" }, "engine.io": { - "version": "6.5.5", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", - "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.2.tgz", + "integrity": "sha512-gmNvsYi9C8iErnZdVcJnvCpSKbWTt1E8+JZo8b+daLninywUWi5NQ5STSHZ9rFjFO7imNcvb8Pc5pe/wMR5xEw==", "requires": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -1200,22 +1211,22 @@ }, "dependencies": { "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -1625,15 +1636,15 @@ "integrity": "sha512-DHH09adx8ltbo/8udr52RcOXggH7HTe0dPmFvTx9iShBl8QAr/WHogup4pU4hCEFWswus8cwNcP7KhTpH5ftCw==" }, "socket.io": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.7.2.tgz", - "integrity": "sha512-bvKVS29/I5fl2FGLNHuXlQaUH/BlzX1IN6S+NKLNZpBsPZIDH+90eQmCs2Railn4YUiww4SzUedJ6+uzwFnKLw==", + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz", + "integrity": "sha512-8U6BEgGjQOfGz3HHTYaC/L1GaxDCJ/KM0XTkJly0EhZ5U/du9uNEZy4ZgYzEzIqlx2CMm25CrCqr1ck899eLNA==", "requires": { "accepts": "~1.3.4", "base64id": "~2.0.0", "cors": "~2.8.5", "debug": "~4.3.2", - "engine.io": "~6.5.2", + "engine.io": "~6.6.0", "socket.io-adapter": "~2.5.2", "socket.io-parser": "~4.2.4" }, @@ -1746,6 +1757,11 @@ "mime-types": "~2.1.24" } }, + "undici-types": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" + }, "unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/examples/client/javascript/package.json b/examples/client/javascript/package.json index a9bc3402..6e806da3 100644 --- a/examples/client/javascript/package.json +++ b/examples/client/javascript/package.json @@ -4,7 +4,7 @@ "dependencies": { "express": "^4.21.1", "smoothie": "1.19.0", - "socket.io": "^4.6.1", + "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" } } From 72d37ea79f4cf6076591782e4781fd4868a7e0d6 Mon Sep 17 00:00:00 2001 From: Peter Bierma Date: Sat, 9 Nov 2024 18:40:38 -0500 Subject: [PATCH 075/173] Fix typo with `AsyncClient.connect` example (#1403) --- src/socketio/async_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index 5fd8daaf..e79fce0e 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -116,7 +116,7 @@ async def connect(self, url, headers={}, auth=None, transports=None, Example usage:: sio = socketio.AsyncClient() - sio.connect('http://localhost:5000') + await sio.connect('http://localhost:5000') """ if self.connected: raise exceptions.ConnectionError('Already connected') From 03b445a7cebf8c4788039ebd94f3d69aa5586c62 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:27:48 +0000 Subject: [PATCH 076/173] Bump aiohttp from 3.10.2 to 3.10.11 in /examples/server/aiohttp (#1406) #nolog Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.2 to 3.10.11. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.2...v3.10.11) --- updated-dependencies: - dependency-name: aiohttp dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/aiohttp/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/aiohttp/requirements.txt b/examples/server/aiohttp/requirements.txt index 6f19551e..2bda3cff 100644 --- a/examples/server/aiohttp/requirements.txt +++ b/examples/server/aiohttp/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.10.2 +aiohttp==3.10.11 async-timeout==1.1.0 chardet==2.3.0 multidict==2.1.4 From 46c6a70046b340fcf6a564bbc81754a0d086e030 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 23 Nov 2024 09:23:02 +0000 Subject: [PATCH 077/173] Bump tornado from 6.4.1 to 6.4.2 in /examples/server/tornado (#1408) #nolog Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.1 to 6.4.2. - [Changelog](https://github.com/tornadoweb/tornado/blob/v6.4.2/docs/releases.rst) - [Commits](https://github.com/tornadoweb/tornado/compare/v6.4.1...v6.4.2) --- updated-dependencies: - dependency-name: tornado dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/tornado/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/tornado/requirements.txt b/examples/server/tornado/requirements.txt index 9719ca24..32346eff 100644 --- a/examples/server/tornado/requirements.txt +++ b/examples/server/tornado/requirements.txt @@ -1,4 +1,4 @@ -tornado==6.4.1 +tornado==6.4.2 python-engineio python_socketio six==1.10.0 From abf336e108b01f44afb473eb86c1dece6360195c Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 24 Nov 2024 19:55:15 +0000 Subject: [PATCH 078/173] Removed dependency on unittest.TestCase base class --- tests/async/test_admin.py | 40 ++++++++++++++--------------- tests/async/test_client.py | 3 +-- tests/async/test_manager.py | 5 ++-- tests/async/test_namespace.py | 5 +--- tests/async/test_pubsub_manager.py | 5 ++-- tests/async/test_server.py | 5 ++-- tests/async/test_simple_client.py | 3 +-- tests/common/test_admin.py | 38 +++++++++++++-------------- tests/common/test_client.py | 3 +-- tests/common/test_manager.py | 5 ++-- tests/common/test_middleware.py | 3 +-- tests/common/test_msgpack_packet.py | 4 +-- tests/common/test_namespace.py | 3 +-- tests/common/test_packet.py | 4 +-- tests/common/test_pubsub_manager.py | 5 ++-- tests/common/test_server.py | 5 ++-- tests/common/test_simple_client.py | 3 +-- 17 files changed, 60 insertions(+), 79 deletions(-) diff --git a/tests/async/test_admin.py b/tests/async/test_admin.py index 4988277f..4f283538 100644 --- a/tests/async/test_admin.py +++ b/tests/async/test_admin.py @@ -2,7 +2,6 @@ import threading import time from unittest import mock -import unittest import pytest try: from engineio.async_socket import AsyncSocket as EngineIOSocket @@ -38,13 +37,13 @@ def connect(sid, environ, auth): pass async def shutdown(): - await instrumented_server.shutdown() + await self.isvr.shutdown() await sio.shutdown() if 'server_stats_interval' not in ikwargs: ikwargs['server_stats_interval'] = 0.25 - instrumented_server = sio.instrument(auth=auth, **ikwargs) + self.isvr = sio.instrument(auth=auth, **ikwargs) server = SocketIOWebServer(sio, on_shutdown=shutdown) server.start() @@ -56,10 +55,11 @@ async def shutdown(): EngineIOSocket.schedule_ping = mock.MagicMock() try: - ret = f(self, instrumented_server, *args, **kwargs) + ret = f(self, *args, **kwargs) finally: server.stop() - instrumented_server.uninstrument() + self.isvr.uninstrument() + self.isvr = None EngineIOSocket.schedule_ping = original_schedule_ping @@ -80,12 +80,12 @@ async def _async_custom_auth(auth): return auth == {'foo': 'bar'} -class TestAsyncAdmin(unittest.TestCase): - def setUp(self): +class TestAsyncAdmin: + def setup_method(self): print('threads at start:', threading.enumerate()) self.thread_count = threading.active_count() - def tearDown(self): + def teardown_method(self): print('threads at end:', threading.enumerate()) assert self.thread_count == threading.active_count() @@ -107,7 +107,7 @@ def test_missing_auth(self): sio.instrument() @with_instrumented_server(auth=False) - def test_admin_connect_with_no_auth(self, isvr): + def test_admin_connect_with_no_auth(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') with socketio.SimpleClient() as admin_client: @@ -115,7 +115,7 @@ def test_admin_connect_with_no_auth(self, isvr): auth={'foo': 'bar'}) @with_instrumented_server(auth={'foo': 'bar'}) - def test_admin_connect_with_dict_auth(self, isvr): + def test_admin_connect_with_dict_auth(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) @@ -131,7 +131,7 @@ def test_admin_connect_with_dict_auth(self, isvr): @with_instrumented_server(auth=[{'foo': 'bar'}, {'u': 'admin', 'p': 'secret'}]) - def test_admin_connect_with_list_auth(self, isvr): + def test_admin_connect_with_list_auth(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) @@ -148,7 +148,7 @@ def test_admin_connect_with_list_auth(self, isvr): namespace='/admin') @with_instrumented_server(auth=_custom_auth) - def test_admin_connect_with_function_auth(self, isvr): + def test_admin_connect_with_function_auth(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) @@ -162,7 +162,7 @@ def test_admin_connect_with_function_auth(self, isvr): namespace='/admin') @with_instrumented_server(auth=_async_custom_auth) - def test_admin_connect_with_async_function_auth(self, isvr): + def test_admin_connect_with_async_function_auth(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) @@ -176,7 +176,7 @@ def test_admin_connect_with_async_function_auth(self, isvr): namespace='/admin') @with_instrumented_server() - def test_admin_connect_only_admin(self, isvr): + def test_admin_connect_only_admin(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') sid = admin_client.sid @@ -201,7 +201,7 @@ def test_admin_connect_only_admin(self, isvr): events['server_stats']['namespaces'] @with_instrumented_server() - def test_admin_connect_with_others(self, isvr): + def test_admin_connect_with_others(self): with socketio.SimpleClient() as client1, \ socketio.SimpleClient() as client2, \ socketio.SimpleClient() as client3, \ @@ -210,12 +210,12 @@ def test_admin_connect_with_others(self, isvr): client1.emit('enter_room', 'room') sid1 = client1.sid - saved_check_for_upgrade = isvr._check_for_upgrade - isvr._check_for_upgrade = AsyncMock() + saved_check_for_upgrade = self.isvr._check_for_upgrade + self.isvr._check_for_upgrade = AsyncMock() client2.connect('http://localhost:8900', namespace='/foo', transports=['polling']) sid2 = client2.sid - isvr._check_for_upgrade = saved_check_for_upgrade + self.isvr._check_for_upgrade = saved_check_for_upgrade client3.connect('http://localhost:8900', namespace='/admin') sid3 = client3.sid @@ -251,7 +251,7 @@ def test_admin_connect_with_others(self, isvr): assert socket['rooms'] == [sid3] @with_instrumented_server(mode='production', read_only=True) - def test_admin_connect_production(self, isvr): + def test_admin_connect_production(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') events = self._expect({'config': 1, 'server_stats': 2}, @@ -272,7 +272,7 @@ def test_admin_connect_production(self, isvr): events['server_stats']['namespaces'] @with_instrumented_server() - def test_admin_features(self, isvr): + def test_admin_features(self): with socketio.SimpleClient() as client1, \ socketio.SimpleClient() as client2, \ socketio.SimpleClient() as admin_client: diff --git a/tests/async/test_client.py b/tests/async/test_client.py index cf99059e..38c690d3 100644 --- a/tests/async/test_client.py +++ b/tests/async/test_client.py @@ -1,5 +1,4 @@ import asyncio -import unittest from unittest import mock import pytest @@ -12,7 +11,7 @@ from .helpers import AsyncMock, _run -class TestAsyncClient(unittest.TestCase): +class TestAsyncClient: def test_is_asyncio_based(self): c = async_client.AsyncClient() assert c.is_asyncio_based() diff --git a/tests/async/test_manager.py b/tests/async/test_manager.py index 83f64752..50d1dd9d 100644 --- a/tests/async/test_manager.py +++ b/tests/async/test_manager.py @@ -1,4 +1,3 @@ -import unittest from unittest import mock from socketio import async_manager @@ -6,8 +5,8 @@ from .helpers import AsyncMock, _run -class TestAsyncManager(unittest.TestCase): - def setUp(self): +class TestAsyncManager: + def setup_method(self): id = 0 def generate_id(): diff --git a/tests/async/test_namespace.py b/tests/async/test_namespace.py index 60430032..873f4791 100644 --- a/tests/async/test_namespace.py +++ b/tests/async/test_namespace.py @@ -1,13 +1,10 @@ -import sys -import unittest from unittest import mock from socketio import async_namespace from .helpers import AsyncMock, _run -@unittest.skipIf(sys.version_info < (3, 5), 'only for Python 3.5+') -class TestAsyncNamespace(unittest.TestCase): +class TestAsyncNamespace: def test_connect_event(self): result = {} diff --git a/tests/async/test_pubsub_manager.py b/tests/async/test_pubsub_manager.py index c7aeb6e9..8a509d23 100644 --- a/tests/async/test_pubsub_manager.py +++ b/tests/async/test_pubsub_manager.py @@ -1,6 +1,5 @@ import asyncio import functools -import unittest from unittest import mock import pytest @@ -11,8 +10,8 @@ from .helpers import AsyncMock, _run -class TestAsyncPubSubManager(unittest.TestCase): - def setUp(self): +class TestAsyncPubSubManager: + def setup_method(self): id = 0 def generate_id(): diff --git a/tests/async/test_server.py b/tests/async/test_server.py index 471e562a..8b2dfe28 100644 --- a/tests/async/test_server.py +++ b/tests/async/test_server.py @@ -1,6 +1,5 @@ import asyncio import logging -import unittest from unittest import mock from engineio import json @@ -18,8 +17,8 @@ @mock.patch('socketio.server.engineio.AsyncServer', **{ 'return_value.generate_id.side_effect': [str(i) for i in range(1, 10)], 'return_value.send_packet': AsyncMock()}) -class TestAsyncServer(unittest.TestCase): - def tearDown(self): +class TestAsyncServer: + def teardown_method(self): # restore JSON encoder, in case a test changed it packet.Packet.json = json diff --git a/tests/async/test_simple_client.py b/tests/async/test_simple_client.py index 08b2ea65..6a2eb7ad 100644 --- a/tests/async/test_simple_client.py +++ b/tests/async/test_simple_client.py @@ -1,5 +1,4 @@ import asyncio -import unittest from unittest import mock import pytest @@ -8,7 +7,7 @@ from .helpers import AsyncMock, _run -class TestAsyncAsyncSimpleClient(unittest.TestCase): +class TestAsyncAsyncSimpleClient: def test_constructor(self): client = AsyncSimpleClient(1, '2', a='3', b=4) assert client.client_args == (1, '2') diff --git a/tests/common/test_admin.py b/tests/common/test_admin.py index 2b2d0164..e7667311 100644 --- a/tests/common/test_admin.py +++ b/tests/common/test_admin.py @@ -2,7 +2,6 @@ import threading import time from unittest import mock -import unittest import pytest from engineio.socket import Socket as EngineIOSocket import socketio @@ -36,7 +35,7 @@ def connect(sid, environ, auth): if 'server_stats_interval' not in ikwargs: ikwargs['server_stats_interval'] = 0.25 - instrumented_server = sio.instrument(auth=auth, **ikwargs) + self.isvr = sio.instrument(auth=auth, **ikwargs) server = SocketIOWebServer(sio) server.start() @@ -48,11 +47,12 @@ def connect(sid, environ, auth): EngineIOSocket.schedule_ping = mock.MagicMock() try: - ret = f(self, instrumented_server, *args, **kwargs) + ret = f(self, *args, **kwargs) finally: server.stop() - instrumented_server.shutdown() - instrumented_server.uninstrument() + self.isvr.shutdown() + self.isvr.uninstrument() + self.isvr = None EngineIOSocket.schedule_ping = original_schedule_ping @@ -69,12 +69,12 @@ def _custom_auth(auth): return auth == {'foo': 'bar'} -class TestAdmin(unittest.TestCase): - def setUp(self): +class TestAdmin: + def setup_method(self): print('threads at start:', threading.enumerate()) self.thread_count = threading.active_count() - def tearDown(self): + def teardown_method(self): print('threads at end:', threading.enumerate()) assert self.thread_count == threading.active_count() @@ -96,7 +96,7 @@ def test_missing_auth(self): sio.instrument() @with_instrumented_server(auth=False) - def test_admin_connect_with_no_auth(self, isvr): + def test_admin_connect_with_no_auth(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') with socketio.SimpleClient() as admin_client: @@ -104,7 +104,7 @@ def test_admin_connect_with_no_auth(self, isvr): auth={'foo': 'bar'}) @with_instrumented_server(auth={'foo': 'bar'}) - def test_admin_connect_with_dict_auth(self, isvr): + def test_admin_connect_with_dict_auth(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) @@ -120,7 +120,7 @@ def test_admin_connect_with_dict_auth(self, isvr): @with_instrumented_server(auth=[{'foo': 'bar'}, {'u': 'admin', 'p': 'secret'}]) - def test_admin_connect_with_list_auth(self, isvr): + def test_admin_connect_with_list_auth(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) @@ -137,7 +137,7 @@ def test_admin_connect_with_list_auth(self, isvr): namespace='/admin') @with_instrumented_server(auth=_custom_auth) - def test_admin_connect_with_function_auth(self, isvr): + def test_admin_connect_with_function_auth(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) @@ -151,7 +151,7 @@ def test_admin_connect_with_function_auth(self, isvr): namespace='/admin') @with_instrumented_server() - def test_admin_connect_only_admin(self, isvr): + def test_admin_connect_only_admin(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') sid = admin_client.sid @@ -176,7 +176,7 @@ def test_admin_connect_only_admin(self, isvr): events['server_stats']['namespaces'] @with_instrumented_server() - def test_admin_connect_with_others(self, isvr): + def test_admin_connect_with_others(self): with socketio.SimpleClient() as client1, \ socketio.SimpleClient() as client2, \ socketio.SimpleClient() as client3, \ @@ -185,12 +185,12 @@ def test_admin_connect_with_others(self, isvr): client1.emit('enter_room', 'room') sid1 = client1.sid - saved_check_for_upgrade = isvr._check_for_upgrade - isvr._check_for_upgrade = mock.MagicMock() + saved_check_for_upgrade = self.isvr._check_for_upgrade + self.isvr._check_for_upgrade = mock.MagicMock() client2.connect('http://localhost:8900', namespace='/foo', transports=['polling']) sid2 = client2.sid - isvr._check_for_upgrade = saved_check_for_upgrade + self.isvr._check_for_upgrade = saved_check_for_upgrade client3.connect('http://localhost:8900', namespace='/admin') sid3 = client3.sid @@ -226,7 +226,7 @@ def test_admin_connect_with_others(self, isvr): assert socket['rooms'] == [sid3] @with_instrumented_server(mode='production', read_only=True) - def test_admin_connect_production(self, isvr): + def test_admin_connect_production(self): with socketio.SimpleClient() as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') events = self._expect({'config': 1, 'server_stats': 2}, @@ -247,7 +247,7 @@ def test_admin_connect_production(self, isvr): events['server_stats']['namespaces'] @with_instrumented_server() - def test_admin_features(self, isvr): + def test_admin_features(self): with socketio.SimpleClient() as client1, \ socketio.SimpleClient() as client2, \ socketio.SimpleClient() as admin_client: diff --git a/tests/common/test_client.py b/tests/common/test_client.py index 52cfc336..c7de4b98 100644 --- a/tests/common/test_client.py +++ b/tests/common/test_client.py @@ -1,6 +1,5 @@ import logging import time -import unittest from unittest import mock from engineio import exceptions as engineio_exceptions @@ -16,7 +15,7 @@ from socketio import packet -class TestClient(unittest.TestCase): +class TestClient: def test_is_asyncio_based(self): c = client.Client() assert not c.is_asyncio_based() diff --git a/tests/common/test_manager.py b/tests/common/test_manager.py index 571d2fdc..65ed1cb2 100644 --- a/tests/common/test_manager.py +++ b/tests/common/test_manager.py @@ -1,4 +1,3 @@ -import unittest from unittest import mock import pytest @@ -7,8 +6,8 @@ from socketio import packet -class TestBaseManager(unittest.TestCase): - def setUp(self): +class TestBaseManager: + def setup_method(self): id = 0 def generate_id(): diff --git a/tests/common/test_middleware.py b/tests/common/test_middleware.py index 8611a041..05795034 100644 --- a/tests/common/test_middleware.py +++ b/tests/common/test_middleware.py @@ -1,10 +1,9 @@ -import unittest from unittest import mock from socketio import middleware -class TestMiddleware(unittest.TestCase): +class TestMiddleware: def test_wsgi_routing(self): mock_wsgi_app = mock.MagicMock() mock_sio_app = 'foo' diff --git a/tests/common/test_msgpack_packet.py b/tests/common/test_msgpack_packet.py index 4930cffb..e0197a27 100644 --- a/tests/common/test_msgpack_packet.py +++ b/tests/common/test_msgpack_packet.py @@ -1,10 +1,8 @@ -import unittest - from socketio import msgpack_packet from socketio import packet -class TestMsgPackPacket(unittest.TestCase): +class TestMsgPackPacket: def test_encode_decode(self): p = msgpack_packet.MsgPackPacket( packet.CONNECT, data={'auth': {'token': '123'}}, namespace='/foo') diff --git a/tests/common/test_namespace.py b/tests/common/test_namespace.py index 7967ceca..8bfa9899 100644 --- a/tests/common/test_namespace.py +++ b/tests/common/test_namespace.py @@ -1,10 +1,9 @@ -import unittest from unittest import mock from socketio import namespace -class TestNamespace(unittest.TestCase): +class TestNamespace: def test_connect_event(self): result = {} diff --git a/tests/common/test_packet.py b/tests/common/test_packet.py index 1dcc8f0a..5682dab0 100644 --- a/tests/common/test_packet.py +++ b/tests/common/test_packet.py @@ -1,11 +1,9 @@ -import unittest - import pytest from socketio import packet -class TestPacket(unittest.TestCase): +class TestPacket: def test_encode_default_packet(self): pkt = packet.Packet() assert pkt.packet_type == packet.EVENT diff --git a/tests/common/test_pubsub_manager.py b/tests/common/test_pubsub_manager.py index 5a4653ec..6d8eda75 100644 --- a/tests/common/test_pubsub_manager.py +++ b/tests/common/test_pubsub_manager.py @@ -1,6 +1,5 @@ import functools import logging -import unittest from unittest import mock import pytest @@ -10,8 +9,8 @@ from socketio import packet -class TestPubSubManager(unittest.TestCase): - def setUp(self): +class TestPubSubManager: + def setup_method(self): id = 0 def generate_id(): diff --git a/tests/common/test_server.py b/tests/common/test_server.py index 33790dc7..e6b02e5a 100644 --- a/tests/common/test_server.py +++ b/tests/common/test_server.py @@ -1,5 +1,4 @@ import logging -import unittest from unittest import mock from engineio import json @@ -15,8 +14,8 @@ @mock.patch('socketio.server.engineio.Server', **{ 'return_value.generate_id.side_effect': [str(i) for i in range(1, 10)]}) -class TestServer(unittest.TestCase): - def tearDown(self): +class TestServer: + def teardown_method(self): # restore JSON encoder, in case a test changed it packet.Packet.json = json diff --git a/tests/common/test_simple_client.py b/tests/common/test_simple_client.py index 3b9f9830..42790573 100644 --- a/tests/common/test_simple_client.py +++ b/tests/common/test_simple_client.py @@ -1,11 +1,10 @@ -import unittest from unittest import mock import pytest from socketio import SimpleClient from socketio.exceptions import SocketIOError, TimeoutError, DisconnectedError -class TestSimpleClient(unittest.TestCase): +class TestSimpleClient: def test_constructor(self): client = SimpleClient(1, '2', a='3', b=4) assert client.client_args == (1, '2') From 8f0e66c1cd1cd63dcef703576cc9cb9c99104df7 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 24 Nov 2024 20:24:13 +0000 Subject: [PATCH 079/173] Adopted unittest.mock.AsyncMock in async unit tests --- tests/async/helpers.py | 12 - tests/async/test_admin.py | 3 +- tests/async/test_client.py | 455 ++++++++++++++--------------- tests/async/test_manager.py | 110 +++---- tests/async/test_namespace.py | 78 ++--- tests/async/test_pubsub_manager.py | 172 ++++++----- tests/async/test_server.py | 240 ++++++++------- tests/async/test_simple_client.py | 40 +-- 8 files changed, 543 insertions(+), 567 deletions(-) diff --git a/tests/async/helpers.py b/tests/async/helpers.py index 09e323c7..c9b708c9 100644 --- a/tests/async/helpers.py +++ b/tests/async/helpers.py @@ -1,16 +1,4 @@ import asyncio -from unittest import mock - - -def AsyncMock(*args, **kwargs): - """Return a mock asynchronous function.""" - m = mock.MagicMock(*args, **kwargs) - - async def mock_coro(*args, **kwargs): - return m(*args, **kwargs) - - mock_coro.mock = m - return mock_coro def _run(coro): diff --git a/tests/async/test_admin.py b/tests/async/test_admin.py index 4f283538..a1cf97c4 100644 --- a/tests/async/test_admin.py +++ b/tests/async/test_admin.py @@ -10,7 +10,6 @@ import socketio from socketio.exceptions import ConnectionError from tests.asyncio_web_server import SocketIOWebServer -from .helpers import AsyncMock def with_instrumented_server(auth=False, **ikwargs): @@ -211,7 +210,7 @@ def test_admin_connect_with_others(self): sid1 = client1.sid saved_check_for_upgrade = self.isvr._check_for_upgrade - self.isvr._check_for_upgrade = AsyncMock() + self.isvr._check_for_upgrade = mock.AsyncMock() client2.connect('http://localhost:8900', namespace='/foo', transports=['polling']) sid2 = client2.sid diff --git a/tests/async/test_client.py b/tests/async/test_client.py index 38c690d3..289abd94 100644 --- a/tests/async/test_client.py +++ b/tests/async/test_client.py @@ -8,7 +8,7 @@ from engineio import exceptions as engineio_exceptions from socketio import exceptions from socketio import packet -from .helpers import AsyncMock, _run +from .helpers import _run class TestAsyncClient: @@ -18,7 +18,7 @@ def test_is_asyncio_based(self): def test_connect(self): c = async_client.AsyncClient() - c.eio.connect = AsyncMock() + c.eio.connect = mock.AsyncMock() _run( c.connect( 'url', @@ -36,7 +36,7 @@ def test_connect(self): assert c.connection_transports == 'transports' assert c.connection_namespaces == ['/foo', '/', '/bar'] assert c.socketio_path == 'path' - c.eio.connect.mock.assert_called_once_with( + c.eio.connect.assert_awaited_once_with( 'url', headers='headers', transports='transports', @@ -48,7 +48,7 @@ async def headers(): return 'headers' c = async_client.AsyncClient() - c.eio.connect = AsyncMock() + c.eio.connect = mock.AsyncMock() _run( c.connect( lambda: 'url', @@ -60,7 +60,7 @@ async def headers(): wait=False, ) ) - c.eio.connect.mock.assert_called_once_with( + c.eio.connect.assert_awaited_once_with( 'url', headers='headers', transports='transports', @@ -69,7 +69,7 @@ async def headers(): def test_connect_one_namespace(self): c = async_client.AsyncClient() - c.eio.connect = AsyncMock() + c.eio.connect = mock.AsyncMock() _run( c.connect( 'url', @@ -85,7 +85,7 @@ def test_connect_one_namespace(self): assert c.connection_transports == 'transports' assert c.connection_namespaces == ['/foo'] assert c.socketio_path == 'path' - c.eio.connect.mock.assert_called_once_with( + c.eio.connect.assert_awaited_once_with( 'url', headers='headers', transports='transports', @@ -94,7 +94,7 @@ def test_connect_one_namespace(self): def test_connect_default_namespaces(self): c = async_client.AsyncClient() - c.eio.connect = AsyncMock() + c.eio.connect = mock.AsyncMock() c.on('foo', mock.MagicMock(), namespace='/foo') c.on('bar', mock.MagicMock(), namespace='/') c.on('baz', mock.MagicMock(), namespace='*') @@ -113,7 +113,7 @@ def test_connect_default_namespaces(self): assert c.connection_namespaces == ['/', '/foo'] or \ c.connection_namespaces == ['/foo', '/'] assert c.socketio_path == 'path' - c.eio.connect.mock.assert_called_once_with( + c.eio.connect.assert_awaited_once_with( 'url', headers='headers', transports='transports', @@ -122,7 +122,7 @@ def test_connect_default_namespaces(self): def test_connect_no_namespaces(self): c = async_client.AsyncClient() - c.eio.connect = AsyncMock() + c.eio.connect = mock.AsyncMock() _run( c.connect( 'url', @@ -137,7 +137,7 @@ def test_connect_no_namespaces(self): assert c.connection_transports == 'transports' assert c.connection_namespaces == ['/'] assert c.socketio_path == 'path' - c.eio.connect.mock.assert_called_once_with( + c.eio.connect.assert_awaited_once_with( 'url', headers='headers', transports='transports', @@ -146,7 +146,7 @@ def test_connect_no_namespaces(self): def test_connect_error(self): c = async_client.AsyncClient() - c.eio.connect = AsyncMock( + c.eio.connect = mock.AsyncMock( side_effect=engineio_exceptions.ConnectionError('foo') ) c.on('foo', mock.MagicMock(), namespace='/foo') @@ -164,7 +164,7 @@ def test_connect_error(self): def test_connect_twice(self): c = async_client.AsyncClient() - c.eio.connect = AsyncMock() + c.eio.connect = mock.AsyncMock() _run( c.connect( 'url', @@ -181,7 +181,7 @@ def test_connect_twice(self): def test_connect_wait_single_namespace(self): c = async_client.AsyncClient() - c.eio.connect = AsyncMock() + c.eio.connect = mock.AsyncMock() c._connect_event = mock.MagicMock() async def mock_connect(): @@ -200,7 +200,7 @@ async def mock_connect(): def test_connect_wait_two_namespaces(self): c = async_client.AsyncClient() - c.eio.connect = AsyncMock() + c.eio.connect = mock.AsyncMock() c._connect_event = mock.MagicMock() async def mock_connect(): @@ -226,8 +226,8 @@ async def mock_connect(): def test_connect_timeout(self): c = async_client.AsyncClient() - c.eio.connect = AsyncMock() - c.disconnect = AsyncMock() + c.eio.connect = mock.AsyncMock() + c.disconnect = mock.AsyncMock() with pytest.raises(exceptions.ConnectionError): _run( c.connect( @@ -236,21 +236,21 @@ def test_connect_timeout(self): wait_timeout=0.01, ) ) - c.disconnect.mock.assert_called_once_with() + c.disconnect.assert_awaited_once_with() def test_wait_no_reconnect(self): c = async_client.AsyncClient() - c.eio.wait = AsyncMock() - c.sleep = AsyncMock() + c.eio.wait = mock.AsyncMock() + c.sleep = mock.AsyncMock() c._reconnect_task = None _run(c.wait()) - c.eio.wait.mock.assert_called_once_with() - c.sleep.mock.assert_called_once_with(1) + c.eio.wait.assert_awaited_once_with() + c.sleep.assert_awaited_once_with(1) def test_wait_reconnect_failed(self): c = async_client.AsyncClient() - c.eio.wait = AsyncMock() - c.sleep = AsyncMock() + c.eio.wait = mock.AsyncMock() + c.sleep = mock.AsyncMock() states = ['disconnected'] async def fake_wait(): @@ -258,13 +258,13 @@ async def fake_wait(): c._reconnect_task = fake_wait() _run(c.wait()) - c.eio.wait.mock.assert_called_once_with() - c.sleep.mock.assert_called_once_with(1) + c.eio.wait.assert_awaited_once_with() + c.sleep.assert_awaited_once_with(1) def test_wait_reconnect_successful(self): c = async_client.AsyncClient() - c.eio.wait = AsyncMock() - c.sleep = AsyncMock() + c.eio.wait = mock.AsyncMock() + c.sleep = mock.AsyncMock() states = ['connected', 'disconnected'] async def fake_wait(): @@ -273,26 +273,26 @@ async def fake_wait(): c._reconnect_task = fake_wait() _run(c.wait()) - assert c.eio.wait.mock.call_count == 2 - assert c.sleep.mock.call_count == 2 + assert c.eio.wait.await_count == 2 + assert c.sleep.await_count == 2 def test_emit_no_arguments(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() _run(c.emit('foo')) expected_packet = packet.Packet( packet.EVENT, namespace='/', data=['foo'], id=None) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_one_argument(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() _run(c.emit('foo', 'bar')) expected_packet = packet.Packet( packet.EVENT, @@ -300,16 +300,16 @@ def test_emit_one_argument(self): data=['foo', 'bar'], id=None, ) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_one_argument_list(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() _run(c.emit('foo', ['bar', 'baz'])) expected_packet = packet.Packet( packet.EVENT, @@ -317,16 +317,16 @@ def test_emit_one_argument_list(self): data=['foo', ['bar', 'baz']], id=None, ) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_two_arguments(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() _run(c.emit('foo', ('bar', 'baz'))) expected_packet = packet.Packet( packet.EVENT, @@ -334,22 +334,22 @@ def test_emit_two_arguments(self): data=['foo', 'bar', 'baz'], id=None, ) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_namespace(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1'} - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() _run(c.emit('foo', namespace='/foo')) expected_packet = packet.Packet( packet.EVENT, namespace='/foo', data=['foo'], id=None) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) @@ -361,15 +361,15 @@ def test_emit_unknown_namespace(self): def test_emit_with_callback(self): c = async_client.AsyncClient() - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() c._generate_ack_id = mock.MagicMock(return_value=123) c.namespaces = {'/': '1'} _run(c.emit('foo', callback='cb')) expected_packet = packet.Packet( packet.EVENT, namespace='/', data=['foo'], id=123) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) c._generate_ack_id.assert_called_once_with('/', 'cb') @@ -377,14 +377,14 @@ def test_emit_with_callback(self): def test_emit_namespace_with_callback(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1'} - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() c._generate_ack_id = mock.MagicMock(return_value=123) _run(c.emit('foo', namespace='/foo', callback='cb')) expected_packet = packet.Packet( packet.EVENT, namespace='/foo', data=['foo'], id=123) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) c._generate_ack_id.assert_called_once_with('/foo', 'cb') @@ -392,7 +392,7 @@ def test_emit_namespace_with_callback(self): def test_emit_binary(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() _run(c.emit('foo', b'bar')) expected_packet = packet.Packet( packet.EVENT, @@ -400,16 +400,16 @@ def test_emit_binary(self): data=['foo', b'bar'], id=None, ) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_not_binary(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() _run(c.emit('foo', 'bar')) expected_packet = packet.Packet( packet.EVENT, @@ -417,25 +417,25 @@ def test_emit_not_binary(self): data=['foo', 'bar'], id=None, ) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) def test_send(self): c = async_client.AsyncClient() - c.emit = AsyncMock() + c.emit = mock.AsyncMock() _run(c.send('data', 'namespace', 'callback')) - c.emit.mock.assert_called_once_with( + c.emit.assert_awaited_once_with( 'message', data='data', namespace='namespace', callback='callback' ) def test_send_with_defaults(self): c = async_client.AsyncClient() - c.emit = AsyncMock() + c.emit = mock.AsyncMock() _run(c.send('data')) - c.emit.mock.assert_called_once_with( + c.emit.assert_awaited_once_with( 'message', data='data', namespace=None, callback=None ) @@ -446,16 +446,16 @@ def test_call(self): async def fake_event_wait(): c._generate_ack_id.call_args_list[0][0][1]('foo', 321) - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() c._generate_ack_id = mock.MagicMock(return_value=123) c.eio = mock.MagicMock() c.eio.create_event.return_value.wait = fake_event_wait assert _run(c.call('foo')) == ('foo', 321) expected_packet = packet.Packet( packet.EVENT, namespace='/', data=['foo'], id=123) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) @@ -466,7 +466,7 @@ def test_call_with_timeout(self): async def fake_event_wait(): await asyncio.sleep(1) - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() c._generate_ack_id = mock.MagicMock(return_value=123) c.eio = mock.MagicMock() c.eio.create_event.return_value.wait = fake_event_wait @@ -474,9 +474,9 @@ async def fake_event_wait(): _run(c.call('foo', timeout=0.01)) expected_packet = packet.Packet( packet.EVENT, namespace='/', data=['foo'], id=123) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) @@ -484,41 +484,41 @@ def test_disconnect(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/': '1'} - c._trigger_event = AsyncMock() - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock() + c._send_packet = mock.AsyncMock() c.eio = mock.MagicMock() - c.eio.disconnect = AsyncMock() + c.eio.disconnect = mock.AsyncMock() c.eio.state = 'connected' _run(c.disconnect()) assert c.connected - assert c._trigger_event.mock.call_count == 0 - assert c._send_packet.mock.call_count == 1 + assert c._trigger_event.await_count == 0 + assert c._send_packet.await_count == 1 expected_packet = packet.Packet(packet.DISCONNECT, namespace='/') assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) - c.eio.disconnect.mock.assert_called_once_with(abort=True) + c.eio.disconnect.assert_awaited_once_with(abort=True) def test_disconnect_namespaces(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1', '/bar': '2'} - c._trigger_event = AsyncMock() - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock() + c._send_packet = mock.AsyncMock() c.eio = mock.MagicMock() - c.eio.disconnect = AsyncMock() + c.eio.disconnect = mock.AsyncMock() c.eio.state = 'connected' _run(c.disconnect()) - assert c._trigger_event.mock.call_count == 0 - assert c._send_packet.mock.call_count == 2 + assert c._trigger_event.await_count == 0 + assert c._send_packet.await_count == 2 expected_packet = packet.Packet(packet.DISCONNECT, namespace='/foo') assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) expected_packet = packet.Packet(packet.DISCONNECT, namespace='/bar') assert ( - c._send_packet.mock.call_args_list[1][0][0].encode() + c._send_packet.await_args_list[1][0][0].encode() == expected_packet.encode() ) @@ -532,103 +532,103 @@ def test_start_background_task(self): def test_sleep(self): c = async_client.AsyncClient() - c.eio.sleep = AsyncMock() + c.eio.sleep = mock.AsyncMock() _run(c.sleep(1.23)) - c.eio.sleep.mock.assert_called_once_with(1.23) + c.eio.sleep.assert_awaited_once_with(1.23) def test_send_packet(self): c = async_client.AsyncClient() - c.eio.send = AsyncMock() + c.eio.send = mock.AsyncMock() _run(c._send_packet(packet.Packet(packet.EVENT, 'foo'))) - c.eio.send.mock.assert_called_once_with('2"foo"') + c.eio.send.assert_awaited_once_with('2"foo"') def test_send_packet_binary(self): c = async_client.AsyncClient() - c.eio.send = AsyncMock() + c.eio.send = mock.AsyncMock() _run(c._send_packet(packet.Packet(packet.EVENT, b'foo'))) - assert c.eio.send.mock.call_args_list == [ + assert c.eio.send.await_args_list == [ mock.call('51-{"_placeholder":true,"num":0}'), mock.call(b'foo'), - ] or c.eio.send.mock.call_args_list == [ + ] or c.eio.send.await_args_list == [ mock.call('51-{"num":0,"_placeholder":true}'), mock.call(b'foo'), ] def test_send_packet_default_binary(self): c = async_client.AsyncClient() - c.eio.send = AsyncMock() + c.eio.send = mock.AsyncMock() _run(c._send_packet(packet.Packet(packet.EVENT, 'foo'))) - c.eio.send.mock.assert_called_once_with('2"foo"') + c.eio.send.assert_awaited_once_with('2"foo"') def test_handle_connect(self): c = async_client.AsyncClient() c._connect_event = mock.MagicMock() - c._trigger_event = AsyncMock() - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock() + c._send_packet = mock.AsyncMock() _run(c._handle_connect('/', {'sid': '123'})) c._connect_event.set.assert_called_once_with() - c._trigger_event.mock.assert_called_once_with('connect', namespace='/') - c._send_packet.mock.assert_not_called() + c._trigger_event.assert_awaited_once_with('connect', namespace='/') + c._send_packet.assert_not_awaited() def test_handle_connect_with_namespaces(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1', '/bar': '2'} c._connect_event = mock.MagicMock() - c._trigger_event = AsyncMock() - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock() + c._send_packet = mock.AsyncMock() _run(c._handle_connect('/', {'sid': '3'})) c._connect_event.set.assert_called_once_with() - c._trigger_event.mock.assert_called_once_with('connect', namespace='/') + c._trigger_event.assert_awaited_once_with('connect', namespace='/') assert c.namespaces == {'/': '3', '/foo': '1', '/bar': '2'} def test_handle_connect_namespace(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1'} c._connect_event = mock.MagicMock() - c._trigger_event = AsyncMock() - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock() + c._send_packet = mock.AsyncMock() _run(c._handle_connect('/foo', {'sid': '123'})) _run(c._handle_connect('/bar', {'sid': '2'})) - assert c._trigger_event.mock.call_count == 1 + assert c._trigger_event.await_count == 1 c._connect_event.set.assert_called_once_with() - c._trigger_event.mock.assert_called_once_with( + c._trigger_event.assert_awaited_once_with( 'connect', namespace='/bar') assert c.namespaces == {'/foo': '1', '/bar': '2'} def test_handle_disconnect(self): c = async_client.AsyncClient() c.connected = True - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() _run(c._handle_disconnect('/')) - c._trigger_event.mock.assert_any_call( + c._trigger_event.assert_any_await( 'disconnect', namespace='/' ) - c._trigger_event.mock.assert_any_call( + c._trigger_event.assert_any_await( '__disconnect_final', namespace='/' ) assert not c.connected _run(c._handle_disconnect('/')) - assert c._trigger_event.mock.call_count == 2 + assert c._trigger_event.await_count == 2 def test_handle_disconnect_namespace(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() _run(c._handle_disconnect('/foo')) - c._trigger_event.mock.assert_any_call( + c._trigger_event.assert_any_await( 'disconnect', namespace='/foo' ) - c._trigger_event.mock.assert_any_call( + c._trigger_event.assert_any_await( '__disconnect_final', namespace='/foo' ) assert c.namespaces == {'/bar': '2'} assert c.connected _run(c._handle_disconnect('/bar')) - c._trigger_event.mock.assert_any_call( + c._trigger_event.assert_any_await( 'disconnect', namespace='/bar' ) - c._trigger_event.mock.assert_any_call( + c._trigger_event.assert_any_await( '__disconnect_final', namespace='/bar' ) assert c.namespaces == {} @@ -638,12 +638,12 @@ def test_handle_disconnect_unknown_namespace(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() _run(c._handle_disconnect('/baz')) - c._trigger_event.mock.assert_any_call( + c._trigger_event.assert_any_await( 'disconnect', namespace='/baz' ) - c._trigger_event.mock.assert_any_call( + c._trigger_event.assert_any_await( '__disconnect_final', namespace='/baz' ) assert c.namespaces == {'/foo': '1', '/bar': '2'} @@ -653,83 +653,82 @@ def test_handle_disconnect_default_namespaces(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() _run(c._handle_disconnect('/')) - c._trigger_event.mock.assert_any_call('disconnect', namespace='/') - c._trigger_event.mock.assert_any_call('__disconnect_final', - namespace='/') + c._trigger_event.assert_any_await('disconnect', namespace='/') + c._trigger_event.assert_any_await('__disconnect_final', namespace='/') assert c.namespaces == {'/foo': '1', '/bar': '2'} assert c.connected def test_handle_event(self): c = async_client.AsyncClient() - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() _run(c._handle_event('/', None, ['foo', ('bar', 'baz')])) - c._trigger_event.mock.assert_called_once_with( + c._trigger_event.assert_awaited_once_with( 'foo', '/', ('bar', 'baz') ) def test_handle_event_with_id_no_arguments(self): c = async_client.AsyncClient() - c._trigger_event = AsyncMock(return_value=None) - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock(return_value=None) + c._send_packet = mock.AsyncMock() _run(c._handle_event('/', 123, ['foo', ('bar', 'baz')])) - c._trigger_event.mock.assert_called_once_with( + c._trigger_event.assert_awaited_once_with( 'foo', '/', ('bar', 'baz') ) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 expected_packet = packet.Packet( packet.ACK, namespace='/', id=123, data=[]) assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) def test_handle_event_with_id_one_argument(self): c = async_client.AsyncClient() - c._trigger_event = AsyncMock(return_value='ret') - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock(return_value='ret') + c._send_packet = mock.AsyncMock() _run(c._handle_event('/', 123, ['foo', ('bar', 'baz')])) - c._trigger_event.mock.assert_called_once_with( + c._trigger_event.assert_awaited_once_with( 'foo', '/', ('bar', 'baz') ) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 expected_packet = packet.Packet( packet.ACK, namespace='/', id=123, data=['ret']) assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) def test_handle_event_with_id_one_list_argument(self): c = async_client.AsyncClient() - c._trigger_event = AsyncMock(return_value=['a', 'b']) - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock(return_value=['a', 'b']) + c._send_packet = mock.AsyncMock() _run(c._handle_event('/', 123, ['foo', ('bar', 'baz')])) - c._trigger_event.mock.assert_called_once_with( + c._trigger_event.assert_awaited_once_with( 'foo', '/', ('bar', 'baz') ) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 expected_packet = packet.Packet( packet.ACK, namespace='/', id=123, data=[['a', 'b']]) assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) def test_handle_event_with_id_two_arguments(self): c = async_client.AsyncClient() - c._trigger_event = AsyncMock(return_value=('a', 'b')) - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock(return_value=('a', 'b')) + c._send_packet = mock.AsyncMock() _run(c._handle_event('/', 123, ['foo', ('bar', 'baz')])) - c._trigger_event.mock.assert_called_once_with( + c._trigger_event.assert_awaited_once_with( 'foo', '/', ('bar', 'baz') ) - assert c._send_packet.mock.call_count == 1 + assert c._send_packet.await_count == 1 expected_packet = packet.Packet( packet.ACK, namespace='/', id=123, data=['a', 'b']) assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) @@ -743,10 +742,10 @@ def test_handle_ack(self): def test_handle_ack_async(self): c = async_client.AsyncClient() - mock_cb = AsyncMock() + mock_cb = mock.AsyncMock() c.callbacks['/foo'] = {123: mock_cb} _run(c._handle_ack('/foo', 123, ['bar', 'baz'])) - mock_cb.mock.assert_called_once_with('bar', 'baz') + mock_cb.assert_awaited_once_with('bar', 'baz') assert 123 not in c.callbacks['/foo'] def test_handle_ack_not_found(self): @@ -761,13 +760,13 @@ def test_handle_error(self): c = async_client.AsyncClient() c.connected = True c._connect_event = mock.MagicMock() - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() c.namespaces = {'/foo': '1', '/bar': '2'} _run(c._handle_error('/', 'error')) assert c.namespaces == {} assert not c.connected c._connect_event.set.assert_called_once_with() - c._trigger_event.mock.assert_called_once_with( + c._trigger_event.assert_awaited_once_with( 'connect_error', '/', 'error' ) @@ -775,25 +774,25 @@ def test_handle_error_with_no_arguments(self): c = async_client.AsyncClient() c.connected = True c._connect_event = mock.MagicMock() - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() c.namespaces = {'/foo': '1', '/bar': '2'} _run(c._handle_error('/', None)) assert c.namespaces == {} assert not c.connected c._connect_event.set.assert_called_once_with() - c._trigger_event.mock.assert_called_once_with('connect_error', '/') + c._trigger_event.assert_awaited_once_with('connect_error', '/') def test_handle_error_namespace(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} c._connect_event = mock.MagicMock() - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() _run(c._handle_error('/bar', ['error', 'message'])) assert c.namespaces == {'/foo': '1'} assert c.connected c._connect_event.set.assert_called_once_with() - c._trigger_event.mock.assert_called_once_with( + c._trigger_event.assert_awaited_once_with( 'connect_error', '/bar', 'error', 'message' ) @@ -802,12 +801,12 @@ def test_handle_error_namespace_with_no_arguments(self): c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} c._connect_event = mock.MagicMock() - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() _run(c._handle_error('/bar', None)) assert c.namespaces == {'/foo': '1'} assert c.connected c._connect_event.set.assert_called_once_with() - c._trigger_event.mock.assert_called_once_with('connect_error', '/bar') + c._trigger_event.assert_awaited_once_with('connect_error', '/bar') def test_handle_error_unknown_namespace(self): c = async_client.AsyncClient() @@ -833,14 +832,14 @@ def test_trigger_event(self): def test_trigger_event_namespace(self): c = async_client.AsyncClient() - handler = AsyncMock() - catchall_handler = AsyncMock() + handler = mock.AsyncMock() + catchall_handler = mock.AsyncMock() c.on('foo', handler, namespace='/bar') c.on('*', catchall_handler, namespace='/bar') _run(c._trigger_event('foo', '/bar', 1, '2')) _run(c._trigger_event('bar', '/bar', 1, '2', 3)) - handler.mock.assert_called_once_with(1, '2') - catchall_handler.mock.assert_called_once_with('bar', 1, '2', 3) + handler.assert_awaited_once_with(1, '2') + catchall_handler.assert_awaited_once_with('bar', 1, '2', 3) def test_trigger_event_class_namespace(self): c = async_client.AsyncClient() @@ -902,19 +901,19 @@ def on_foo(self, a, b): @mock.patch( 'asyncio.wait_for', - new_callable=AsyncMock, + new_callable=mock.AsyncMock, side_effect=asyncio.TimeoutError, ) @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) def test_handle_reconnect(self, random, wait_for): c = async_client.AsyncClient() c._reconnect_task = 'foo' - c.connect = AsyncMock( + c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] ) _run(c._handle_reconnect()) - assert wait_for.mock.call_count == 3 - assert [x[0][1] for x in asyncio.wait_for.mock.call_args_list] == [ + assert wait_for.await_count == 3 + assert [x[0][1] for x in asyncio.wait_for.await_args_list] == [ 1.5, 1.5, 4.0, @@ -923,19 +922,19 @@ def test_handle_reconnect(self, random, wait_for): @mock.patch( 'asyncio.wait_for', - new_callable=AsyncMock, + new_callable=mock.AsyncMock, side_effect=asyncio.TimeoutError, ) @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) def test_handle_reconnect_max_delay(self, random, wait_for): c = async_client.AsyncClient(reconnection_delay_max=3) c._reconnect_task = 'foo' - c.connect = AsyncMock( + c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] ) _run(c._handle_reconnect()) - assert wait_for.mock.call_count == 3 - assert [x[0][1] for x in asyncio.wait_for.mock.call_args_list] == [ + assert wait_for.await_count == 3 + assert [x[0][1] for x in asyncio.wait_for.await_args_list] == [ 1.5, 1.5, 3.0, @@ -944,7 +943,7 @@ def test_handle_reconnect_max_delay(self, random, wait_for): @mock.patch( 'asyncio.wait_for', - new_callable=AsyncMock, + new_callable=mock.AsyncMock, side_effect=asyncio.TimeoutError, ) @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) @@ -952,23 +951,23 @@ def test_handle_reconnect_max_attempts(self, random, wait_for): c = async_client.AsyncClient(reconnection_attempts=2, logger=True) c.connection_namespaces = ['/'] c._reconnect_task = 'foo' - c._trigger_event = AsyncMock() - c.connect = AsyncMock( + c._trigger_event = mock.AsyncMock() + c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] ) _run(c._handle_reconnect()) - assert wait_for.mock.call_count == 2 - assert [x[0][1] for x in asyncio.wait_for.mock.call_args_list] == [ + assert wait_for.await_count == 2 + assert [x[0][1] for x in asyncio.wait_for.await_args_list] == [ 1.5, 1.5, ] assert c._reconnect_task == 'foo' - c._trigger_event.mock.assert_called_once_with('__disconnect_final', - namespace='/') + c._trigger_event.assert_awaited_once_with('__disconnect_final', + namespace='/') @mock.patch( 'asyncio.wait_for', - new_callable=AsyncMock, + new_callable=mock.AsyncMock, side_effect=[asyncio.TimeoutError, None], ) @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) @@ -976,59 +975,59 @@ def test_handle_reconnect_aborted(self, random, wait_for): c = async_client.AsyncClient(logger=True) c.connection_namespaces = ['/'] c._reconnect_task = 'foo' - c._trigger_event = AsyncMock() - c.connect = AsyncMock( + c._trigger_event = mock.AsyncMock() + c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] ) _run(c._handle_reconnect()) - assert wait_for.mock.call_count == 2 - assert [x[0][1] for x in asyncio.wait_for.mock.call_args_list] == [ + assert wait_for.await_count == 2 + assert [x[0][1] for x in asyncio.wait_for.await_args_list] == [ 1.5, 1.5, ] assert c._reconnect_task == 'foo' - c._trigger_event.mock.assert_called_once_with('__disconnect_final', - namespace='/') + c._trigger_event.assert_awaited_once_with('__disconnect_final', + namespace='/') def test_shutdown_disconnect(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/': '1'} - c._trigger_event = AsyncMock() - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock() + c._send_packet = mock.AsyncMock() c.eio = mock.MagicMock() - c.eio.disconnect = AsyncMock() + c.eio.disconnect = mock.AsyncMock() c.eio.state = 'connected' _run(c.shutdown()) - assert c._trigger_event.mock.call_count == 0 - assert c._send_packet.mock.call_count == 1 + assert c._trigger_event.await_count == 0 + assert c._send_packet.await_count == 1 expected_packet = packet.Packet(packet.DISCONNECT, namespace='/') assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) - c.eio.disconnect.mock.assert_called_once_with(abort=True) + c.eio.disconnect.assert_awaited_once_with(abort=True) def test_shutdown_disconnect_namespaces(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} - c._trigger_event = AsyncMock() - c._send_packet = AsyncMock() + c._trigger_event = mock.AsyncMock() + c._send_packet = mock.AsyncMock() c.eio = mock.MagicMock() - c.eio.disconnect = AsyncMock() + c.eio.disconnect = mock.AsyncMock() c.eio.state = 'connected' _run(c.shutdown()) - assert c._trigger_event.mock.call_count == 0 - assert c._send_packet.mock.call_count == 2 + assert c._trigger_event.await_count == 0 + assert c._send_packet.await_count == 2 expected_packet = packet.Packet(packet.DISCONNECT, namespace='/foo') assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) expected_packet = packet.Packet(packet.DISCONNECT, namespace='/bar') assert ( - c._send_packet.mock.call_args_list[1][0][0].encode() + c._send_packet.await_args_list[1][0][0].encode() == expected_packet.encode() ) @@ -1036,9 +1035,9 @@ def test_shutdown_disconnect_namespaces(self): def test_shutdown_reconnect(self, random): c = async_client.AsyncClient() c.connection_namespaces = ['/'] - c._reconnect_task = AsyncMock()() - c._trigger_event = AsyncMock() - c.connect = AsyncMock(side_effect=exceptions.ConnectionError) + c._reconnect_task = mock.AsyncMock()() + c._trigger_event = mock.AsyncMock() + c.connect = mock.AsyncMock(side_effect=exceptions.ConnectionError) async def r(): task = c.start_background_task(c._handle_reconnect) @@ -1047,29 +1046,29 @@ async def r(): await task _run(r()) - c._trigger_event.mock.assert_called_once_with('__disconnect_final', - namespace='/') + c._trigger_event.assert_awaited_once_with('__disconnect_final', + namespace='/') def test_handle_eio_connect(self): c = async_client.AsyncClient() c.connection_namespaces = ['/', '/foo'] c.connection_auth = 'auth' - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() c.eio.sid = 'foo' assert c.sid is None _run(c._handle_eio_connect()) assert c.sid == 'foo' - assert c._send_packet.mock.call_count == 2 + assert c._send_packet.await_count == 2 expected_packet = packet.Packet( packet.CONNECT, data='auth', namespace='/') assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) expected_packet = packet.Packet( packet.CONNECT, data='auth', namespace='/foo') assert ( - c._send_packet.mock.call_args_list[1][0][0].encode() + c._send_packet.await_args_list[1][0][0].encode() == expected_packet.encode() ) @@ -1077,59 +1076,59 @@ def test_handle_eio_connect_function(self): c = async_client.AsyncClient() c.connection_namespaces = ['/', '/foo'] c.connection_auth = lambda: 'auth' - c._send_packet = AsyncMock() + c._send_packet = mock.AsyncMock() c.eio.sid = 'foo' assert c.sid is None _run(c._handle_eio_connect()) assert c.sid == 'foo' - assert c._send_packet.mock.call_count == 2 + assert c._send_packet.await_count == 2 expected_packet = packet.Packet( packet.CONNECT, data='auth', namespace='/') assert ( - c._send_packet.mock.call_args_list[0][0][0].encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) expected_packet = packet.Packet( packet.CONNECT, data='auth', namespace='/foo') assert ( - c._send_packet.mock.call_args_list[1][0][0].encode() + c._send_packet.await_args_list[1][0][0].encode() == expected_packet.encode() ) def test_handle_eio_message(self): c = async_client.AsyncClient() - c._handle_connect = AsyncMock() - c._handle_disconnect = AsyncMock() - c._handle_event = AsyncMock() - c._handle_ack = AsyncMock() - c._handle_error = AsyncMock() + c._handle_connect = mock.AsyncMock() + c._handle_disconnect = mock.AsyncMock() + c._handle_event = mock.AsyncMock() + c._handle_ack = mock.AsyncMock() + c._handle_error = mock.AsyncMock() _run(c._handle_eio_message('0{"sid":"123"}')) - c._handle_connect.mock.assert_called_with(None, {'sid': '123'}) + c._handle_connect.assert_awaited_with(None, {'sid': '123'}) _run(c._handle_eio_message('0/foo,{"sid":"123"}')) - c._handle_connect.mock.assert_called_with('/foo', {'sid': '123'}) + c._handle_connect.assert_awaited_with('/foo', {'sid': '123'}) _run(c._handle_eio_message('1')) - c._handle_disconnect.mock.assert_called_with(None) + c._handle_disconnect.assert_awaited_with(None) _run(c._handle_eio_message('1/foo')) - c._handle_disconnect.mock.assert_called_with('/foo') + c._handle_disconnect.assert_awaited_with('/foo') _run(c._handle_eio_message('2["foo"]')) - c._handle_event.mock.assert_called_with(None, None, ['foo']) + c._handle_event.assert_awaited_with(None, None, ['foo']) _run(c._handle_eio_message('3/foo,["bar"]')) - c._handle_ack.mock.assert_called_with('/foo', None, ['bar']) + c._handle_ack.assert_awaited_with('/foo', None, ['bar']) _run(c._handle_eio_message('4')) - c._handle_error.mock.assert_called_with(None, None) + c._handle_error.assert_awaited_with(None, None) _run(c._handle_eio_message('4"foo"')) - c._handle_error.mock.assert_called_with(None, 'foo') + c._handle_error.assert_awaited_with(None, 'foo') _run(c._handle_eio_message('4["foo"]')) - c._handle_error.mock.assert_called_with(None, ['foo']) + c._handle_error.assert_awaited_with(None, ['foo']) _run(c._handle_eio_message('4/foo')) - c._handle_error.mock.assert_called_with('/foo', None) + c._handle_error.assert_awaited_with('/foo', None) _run(c._handle_eio_message('4/foo,["foo","bar"]')) - c._handle_error.mock.assert_called_with('/foo', ['foo', 'bar']) + c._handle_error.assert_awaited_with('/foo', ['foo', 'bar']) _run(c._handle_eio_message('51-{"_placeholder":true,"num":0}')) assert c._binary_packet.packet_type == packet.BINARY_EVENT _run(c._handle_eio_message(b'foo')) - c._handle_event.mock.assert_called_with(None, None, b'foo') + c._handle_event.assert_awaited_with(None, None, b'foo') _run( c._handle_eio_message( '62-/foo,{"1":{"_placeholder":true,"num":1},' @@ -1139,7 +1138,7 @@ def test_handle_eio_message(self): assert c._binary_packet.packet_type == packet.BINARY_ACK _run(c._handle_eio_message(b'bar')) _run(c._handle_eio_message(b'foo')) - c._handle_ack.mock.assert_called_with( + c._handle_ack.assert_awaited_with( '/foo', None, {'1': b'foo', '2': b'bar'} ) with pytest.raises(ValueError): @@ -1149,12 +1148,12 @@ def test_eio_disconnect(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} c.connected = True - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() c.start_background_task = mock.MagicMock() c.sid = 'foo' c.eio.state = 'connected' _run(c._handle_eio_disconnect()) - c._trigger_event.mock.assert_called_once_with( + c._trigger_event.assert_awaited_once_with( 'disconnect', namespace='/' ) assert c.sid is None @@ -1164,12 +1163,12 @@ def test_eio_disconnect_namespaces(self): c = async_client.AsyncClient(reconnection=False) c.namespaces = {'/foo': '1', '/bar': '2'} c.connected = True - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() c.sid = 'foo' c.eio.state = 'connected' _run(c._handle_eio_disconnect()) - c._trigger_event.mock.assert_any_call('disconnect', namespace='/foo') - c._trigger_event.mock.assert_any_call('disconnect', namespace='/bar') + c._trigger_event.assert_any_await('disconnect', namespace='/foo') + c._trigger_event.assert_any_await('disconnect', namespace='/bar') assert c.sid is None assert not c.connected @@ -1191,15 +1190,15 @@ def test_eio_disconnect_no_reconnect(self): c = async_client.AsyncClient(reconnection=False) c.namespaces = {'/': '1'} c.connected = True - c._trigger_event = AsyncMock() + c._trigger_event = mock.AsyncMock() c.start_background_task = mock.MagicMock() c.sid = 'foo' c.eio.state = 'connected' _run(c._handle_eio_disconnect()) - c._trigger_event.mock.assert_any_call( + c._trigger_event.assert_any_await( 'disconnect', namespace='/' ) - c._trigger_event.mock.assert_any_call( + c._trigger_event.assert_any_await( '__disconnect_final', namespace='/' ) assert c.sid is None diff --git a/tests/async/test_manager.py b/tests/async/test_manager.py index 50d1dd9d..5d60e039 100644 --- a/tests/async/test_manager.py +++ b/tests/async/test_manager.py @@ -2,7 +2,7 @@ from socketio import async_manager from socketio import packet -from .helpers import AsyncMock, _run +from .helpers import _run class TestAsyncManager: @@ -15,8 +15,8 @@ def generate_id(): return str(id) mock_server = mock.MagicMock() - mock_server._send_packet = AsyncMock() - mock_server._send_eio_packet = AsyncMock() + mock_server._send_packet = mock.AsyncMock() + mock_server._send_eio_packet = mock.AsyncMock() mock_server.eio.generate_id = generate_id mock_server.packet_class = packet.Packet self.bm = async_manager.AsyncManager() @@ -128,14 +128,14 @@ def test_trigger_sync_callback(self): def test_trigger_async_callback(self): sid1 = _run(self.bm.connect('123', '/')) sid2 = _run(self.bm.connect('123', '/foo')) - cb = AsyncMock() + cb = mock.AsyncMock() id1 = self.bm._generate_ack_id(sid1, cb) id2 = self.bm._generate_ack_id(sid2, cb) _run(self.bm.trigger_callback(sid1, id1, ['foo'])) _run(self.bm.trigger_callback(sid2, id2, ['bar', 'baz'])) - assert cb.mock.call_count == 2 - cb.mock.assert_any_call('foo') - cb.mock.assert_any_call('bar', 'baz') + assert cb.await_count == 2 + cb.assert_any_await('foo') + cb.assert_any_await('bar', 'baz') def test_invalid_callback(self): sid = _run(self.bm.connect('123', '/')) @@ -145,7 +145,7 @@ def test_invalid_callback(self): # these should not raise an exception _run(self.bm.trigger_callback('xxx', id, ['foo'])) _run(self.bm.trigger_callback(sid, id + 1, ['foo'])) - assert cb.mock.call_count == 0 + assert cb.call_count == 0 def test_get_namespaces(self): assert list(self.bm.get_namespaces()) == [] @@ -207,10 +207,10 @@ def test_emit_to_sid(self): 'my event', {'foo': 'bar'}, namespace='/foo', to=sid ) ) - assert self.bm.server._send_eio_packet.mock.call_count == 1 - assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_eio_packet.await_count == 1 + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1] + pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' def test_emit_to_room(self): @@ -224,13 +224,13 @@ def test_emit_to_room(self): 'my event', {'foo': 'bar'}, namespace='/foo', room='bar' ) ) - assert self.bm.server._send_eio_packet.mock.call_count == 2 - assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_eio_packet.await_count == 2 + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' - assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] \ + assert self.bm.server._send_eio_packet.await_args_list[1][0][0] \ == '456' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1] - assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] \ + pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] + assert self.bm.server._send_eio_packet.await_args_list[1][0][1] \ == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' @@ -246,17 +246,17 @@ def test_emit_to_rooms(self): self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo', room=['bar', 'baz']) ) - assert self.bm.server._send_eio_packet.mock.call_count == 3 - assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_eio_packet.await_count == 3 + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' - assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] \ + assert self.bm.server._send_eio_packet.await_args_list[1][0][0] \ == '456' - assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][0] \ + assert self.bm.server._send_eio_packet.await_args_list[2][0][0] \ == '789' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1] - assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] \ + pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] + assert self.bm.server._send_eio_packet.await_args_list[1][0][1] \ == pkt - assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][1] \ + assert self.bm.server._send_eio_packet.await_args_list[2][0][1] \ == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' @@ -268,17 +268,17 @@ def test_emit_to_all(self): _run(self.bm.connect('789', '/foo')) _run(self.bm.connect('abc', '/bar')) _run(self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo')) - assert self.bm.server._send_eio_packet.mock.call_count == 3 - assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_eio_packet.await_count == 3 + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' - assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] \ + assert self.bm.server._send_eio_packet.await_args_list[1][0][0] \ == '456' - assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][0] \ + assert self.bm.server._send_eio_packet.await_args_list[2][0][0] \ == '789' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1] - assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] \ + pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] + assert self.bm.server._send_eio_packet.await_args_list[1][0][1] \ == pkt - assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][1] \ + assert self.bm.server._send_eio_packet.await_args_list[2][0][1] \ == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' @@ -294,13 +294,13 @@ def test_emit_to_all_skip_one(self): 'my event', {'foo': 'bar'}, namespace='/foo', skip_sid=sid2 ) ) - assert self.bm.server._send_eio_packet.mock.call_count == 2 - assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_eio_packet.await_count == 2 + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' - assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] \ + assert self.bm.server._send_eio_packet.await_args_list[1][0][0] \ == '789' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1] - assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] \ + pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] + assert self.bm.server._send_eio_packet.await_args_list[1][0][1] \ == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' @@ -319,10 +319,10 @@ def test_emit_to_all_skip_two(self): skip_sid=[sid1, sid3], ) ) - assert self.bm.server._send_eio_packet.mock.call_count == 1 - assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_eio_packet.await_count == 1 + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '456' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1] + pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' def test_emit_with_callback(self): @@ -335,10 +335,10 @@ def test_emit_with_callback(self): ) ) self.bm._generate_ack_id.assert_called_once_with(sid, 'cb') - assert self.bm.server._send_packet.mock.call_count == 1 - assert self.bm.server._send_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_packet.await_count == 1 + assert self.bm.server._send_packet.await_args_list[0][0][0] \ == '123' - pkt = self.bm.server._send_packet.mock.call_args_list[0][0][1] + pkt = self.bm.server._send_packet.await_args_list[0][0][1] assert pkt.encode() == '2/foo,11["my event",{"foo":"bar"}]' def test_emit_to_invalid_room(self): @@ -356,10 +356,10 @@ def test_emit_with_tuple(self): 'my event', ('foo', 'bar'), namespace='/foo', room=sid ) ) - assert self.bm.server._send_eio_packet.mock.call_count == 1 - assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_eio_packet.await_count == 1 + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1] + pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event","foo","bar"]' def test_emit_with_list(self): @@ -369,10 +369,10 @@ def test_emit_with_list(self): 'my event', ['foo', 'bar'], namespace='/foo', room=sid ) ) - assert self.bm.server._send_eio_packet.mock.call_count == 1 - assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_eio_packet.await_count == 1 + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1] + pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event",["foo","bar"]]' def test_emit_with_none(self): @@ -382,10 +382,10 @@ def test_emit_with_none(self): 'my event', None, namespace='/foo', room=sid ) ) - assert self.bm.server._send_eio_packet.mock.call_count == 1 - assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_eio_packet.await_count == 1 + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1] + pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event"]' def test_emit_binary(self): @@ -395,12 +395,12 @@ def test_emit_binary(self): u'my event', b'my binary data', namespace='/', room=sid ) ) - assert self.bm.server._send_eio_packet.mock.call_count == 2 - assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + assert self.bm.server._send_eio_packet.await_count == 2 + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1] + pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '451-["my event",{"_placeholder":true,"num":0}]' - assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] \ + assert self.bm.server._send_eio_packet.await_args_list[1][0][0] \ == '123' - pkt = self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] + pkt = self.bm.server._send_eio_packet.await_args_list[1][0][1] assert pkt.encode() == b'my binary data' diff --git a/tests/async/test_namespace.py b/tests/async/test_namespace.py index 873f4791..62560159 100644 --- a/tests/async/test_namespace.py +++ b/tests/async/test_namespace.py @@ -1,7 +1,7 @@ from unittest import mock from socketio import async_namespace -from .helpers import AsyncMock, _run +from .helpers import _run class TestAsyncNamespace: @@ -70,14 +70,14 @@ async def on_custom_message(self, sid, data): def test_emit(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() - mock_server.emit = AsyncMock() + mock_server.emit = mock.AsyncMock() ns._set_server(mock_server) _run( ns.emit( 'ev', data='data', to='room', skip_sid='skip', callback='cb' ) ) - ns.server.emit.mock.assert_called_with( + ns.server.emit.assert_awaited_with( 'ev', data='data', to='room', @@ -98,7 +98,7 @@ def test_emit(self): ignore_queue=True, ) ) - ns.server.emit.mock.assert_called_with( + ns.server.emit.assert_awaited_with( 'ev', data='data', to=None, @@ -112,10 +112,10 @@ def test_emit(self): def test_send(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() - mock_server.send = AsyncMock() + mock_server.send = mock.AsyncMock() ns._set_server(mock_server) _run(ns.send(data='data', to='room', skip_sid='skip', callback='cb')) - ns.server.send.mock.assert_called_with( + ns.server.send.assert_awaited_with( 'data', to='room', room=None, @@ -134,7 +134,7 @@ def test_send(self): ignore_queue=True, ) ) - ns.server.send.mock.assert_called_with( + ns.server.send.assert_awaited_with( 'data', to=None, room='room', @@ -147,10 +147,10 @@ def test_send(self): def test_call(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() - mock_server.call = AsyncMock() + mock_server.call = mock.AsyncMock() ns._set_server(mock_server) _run(ns.call('ev', data='data', to='sid')) - ns.server.call.mock.assert_called_with( + ns.server.call.assert_awaited_with( 'ev', data='data', to='sid', @@ -161,7 +161,7 @@ def test_call(self): ) _run(ns.call('ev', data='data', sid='sid', namespace='/bar', timeout=45, ignore_queue=True)) - ns.server.call.mock.assert_called_with( + ns.server.call.assert_awaited_with( 'ev', data='data', to=None, @@ -174,40 +174,40 @@ def test_call(self): def test_enter_room(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() - mock_server.enter_room = AsyncMock() + mock_server.enter_room = mock.AsyncMock() ns._set_server(mock_server) _run(ns.enter_room('sid', 'room')) - ns.server.enter_room.mock.assert_called_with( + ns.server.enter_room.assert_awaited_with( 'sid', 'room', namespace='/foo' ) _run(ns.enter_room('sid', 'room', namespace='/bar')) - ns.server.enter_room.mock.assert_called_with( + ns.server.enter_room.assert_awaited_with( 'sid', 'room', namespace='/bar' ) def test_leave_room(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() - mock_server.leave_room = AsyncMock() + mock_server.leave_room = mock.AsyncMock() ns._set_server(mock_server) _run(ns.leave_room('sid', 'room')) - ns.server.leave_room.mock.assert_called_with( + ns.server.leave_room.assert_awaited_with( 'sid', 'room', namespace='/foo' ) _run(ns.leave_room('sid', 'room', namespace='/bar')) - ns.server.leave_room.mock.assert_called_with( + ns.server.leave_room.assert_awaited_with( 'sid', 'room', namespace='/bar' ) def test_close_room(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() - mock_server.close_room = AsyncMock() + mock_server.close_room = mock.AsyncMock() ns._set_server(mock_server) _run(ns.close_room('room')) - ns.server.close_room.mock.assert_called_with('room', namespace='/foo') + ns.server.close_room.assert_awaited_with('room', namespace='/foo') _run(ns.close_room('room', namespace='/bar')) - ns.server.close_room.mock.assert_called_with('room', namespace='/bar') + ns.server.close_room.assert_awaited_with('room', namespace='/bar') def test_rooms(self): ns = async_namespace.AsyncNamespace('/foo') @@ -220,19 +220,19 @@ def test_rooms(self): def test_session(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() - mock_server.get_session = AsyncMock() - mock_server.save_session = AsyncMock() + mock_server.get_session = mock.AsyncMock() + mock_server.save_session = mock.AsyncMock() ns._set_server(mock_server) _run(ns.get_session('sid')) - ns.server.get_session.mock.assert_called_with('sid', namespace='/foo') + ns.server.get_session.assert_awaited_with('sid', namespace='/foo') _run(ns.get_session('sid', namespace='/bar')) - ns.server.get_session.mock.assert_called_with('sid', namespace='/bar') + ns.server.get_session.assert_awaited_with('sid', namespace='/bar') _run(ns.save_session('sid', {'a': 'b'})) - ns.server.save_session.mock.assert_called_with( + ns.server.save_session.assert_awaited_with( 'sid', {'a': 'b'}, namespace='/foo' ) _run(ns.save_session('sid', {'a': 'b'}, namespace='/bar')) - ns.server.save_session.mock.assert_called_with( + ns.server.save_session.assert_awaited_with( 'sid', {'a': 'b'}, namespace='/bar' ) ns.session('sid') @@ -243,12 +243,12 @@ def test_session(self): def test_disconnect(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() - mock_server.disconnect = AsyncMock() + mock_server.disconnect = mock.AsyncMock() ns._set_server(mock_server) _run(ns.disconnect('sid')) - ns.server.disconnect.mock.assert_called_with('sid', namespace='/foo') + ns.server.disconnect.assert_awaited_with('sid', namespace='/foo') _run(ns.disconnect('sid', namespace='/bar')) - ns.server.disconnect.mock.assert_called_with('sid', namespace='/bar') + ns.server.disconnect.assert_awaited_with('sid', namespace='/bar') def test_sync_event_client(self): result = {} @@ -291,49 +291,49 @@ async def on_custom_message(self, sid, data): def test_emit_client(self): ns = async_namespace.AsyncClientNamespace('/foo') mock_client = mock.MagicMock() - mock_client.emit = AsyncMock() + mock_client.emit = mock.AsyncMock() ns._set_client(mock_client) _run(ns.emit('ev', data='data', callback='cb')) - ns.client.emit.mock.assert_called_with( + ns.client.emit.assert_awaited_with( 'ev', data='data', namespace='/foo', callback='cb' ) _run(ns.emit('ev', data='data', namespace='/bar', callback='cb')) - ns.client.emit.mock.assert_called_with( + ns.client.emit.assert_awaited_with( 'ev', data='data', namespace='/bar', callback='cb' ) def test_send_client(self): ns = async_namespace.AsyncClientNamespace('/foo') mock_client = mock.MagicMock() - mock_client.send = AsyncMock() + mock_client.send = mock.AsyncMock() ns._set_client(mock_client) _run(ns.send(data='data', callback='cb')) - ns.client.send.mock.assert_called_with( + ns.client.send.assert_awaited_with( 'data', namespace='/foo', callback='cb' ) _run(ns.send(data='data', namespace='/bar', callback='cb')) - ns.client.send.mock.assert_called_with( + ns.client.send.assert_awaited_with( 'data', namespace='/bar', callback='cb' ) def test_call_client(self): ns = async_namespace.AsyncClientNamespace('/foo') mock_client = mock.MagicMock() - mock_client.call = AsyncMock() + mock_client.call = mock.AsyncMock() ns._set_client(mock_client) _run(ns.call('ev', data='data')) - ns.client.call.mock.assert_called_with( + ns.client.call.assert_awaited_with( 'ev', data='data', namespace='/foo', timeout=None ) _run(ns.call('ev', data='data', namespace='/bar', timeout=45)) - ns.client.call.mock.assert_called_with( + ns.client.call.assert_awaited_with( 'ev', data='data', namespace='/bar', timeout=45 ) def test_disconnect_client(self): ns = async_namespace.AsyncClientNamespace('/foo') mock_client = mock.MagicMock() - mock_client.disconnect = AsyncMock() + mock_client.disconnect = mock.AsyncMock() ns._set_client(mock_client) _run(ns.disconnect()) - ns.client.disconnect.mock.assert_called_with() + ns.client.disconnect.assert_awaited_with() diff --git a/tests/async/test_pubsub_manager.py b/tests/async/test_pubsub_manager.py index 8a509d23..46928827 100644 --- a/tests/async/test_pubsub_manager.py +++ b/tests/async/test_pubsub_manager.py @@ -7,7 +7,7 @@ from socketio import async_manager from socketio import async_pubsub_manager from socketio import packet -from .helpers import AsyncMock, _run +from .helpers import _run class TestAsyncPubSubManager: @@ -22,11 +22,11 @@ def generate_id(): mock_server = mock.MagicMock() mock_server.eio.generate_id = generate_id mock_server.packet_class = packet.Packet - mock_server._send_packet = AsyncMock() - mock_server._send_eio_packet = AsyncMock() - mock_server.disconnect = AsyncMock() + mock_server._send_packet = mock.AsyncMock() + mock_server._send_eio_packet = mock.AsyncMock() + mock_server.disconnect = mock.AsyncMock() self.pm = async_pubsub_manager.AsyncPubSubManager() - self.pm._publish = AsyncMock() + self.pm._publish = mock.AsyncMock() self.pm.set_server(mock_server) self.pm.host_id = '123456' self.pm.initialize() @@ -53,7 +53,7 @@ def test_write_only_init(self): def test_emit(self): _run(self.pm.emit('foo', 'bar')) - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( { 'method': 'emit', 'event': 'foo', @@ -69,7 +69,7 @@ def test_emit(self): def test_emit_with_to(self): sid = 'room-mate' _run(self.pm.emit('foo', 'bar', to=sid)) - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( { 'method': 'emit', 'event': 'foo', @@ -84,7 +84,7 @@ def test_emit_with_to(self): def test_emit_with_namespace(self): _run(self.pm.emit('foo', 'bar', namespace='/baz')) - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( { 'method': 'emit', 'event': 'foo', @@ -99,7 +99,7 @@ def test_emit_with_namespace(self): def test_emit_with_room(self): _run(self.pm.emit('foo', 'bar', room='baz')) - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( { 'method': 'emit', 'event': 'foo', @@ -114,7 +114,7 @@ def test_emit_with_room(self): def test_emit_with_skip_sid(self): _run(self.pm.emit('foo', 'bar', skip_sid='baz')) - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( { 'method': 'emit', 'event': 'foo', @@ -132,7 +132,7 @@ def test_emit_with_callback(self): self.pm, '_generate_ack_id', return_value='123' ): _run(self.pm.emit('foo', 'bar', room='baz', callback='cb')) - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( { 'method': 'emit', 'event': 'foo', @@ -164,25 +164,25 @@ def test_emit_with_ignore_queue(self): 'foo', 'bar', room=sid, namespace='/', ignore_queue=True ) ) - self.pm._publish.mock.assert_not_called() - assert self.pm.server._send_eio_packet.mock.call_count == 1 - assert self.pm.server._send_eio_packet.mock.call_args_list[0][0][0] \ + self.pm._publish.assert_not_awaited() + assert self.pm.server._send_eio_packet.await_count == 1 + assert self.pm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' - pkt = self.pm.server._send_eio_packet.mock.call_args_list[0][0][1] + pkt = self.pm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42["foo","bar"]' def test_can_disconnect(self): sid = _run(self.pm.connect('123', '/')) assert _run(self.pm.can_disconnect(sid, '/')) is True _run(self.pm.can_disconnect(sid, '/foo')) - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( {'method': 'disconnect', 'sid': sid, 'namespace': '/foo', 'host_id': '123456'} ) def test_disconnect(self): _run(self.pm.disconnect('foo', '/')) - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( {'method': 'disconnect', 'sid': 'foo', 'namespace': '/', 'host_id': '123456'} ) @@ -191,7 +191,7 @@ def test_disconnect_ignore_queue(self): sid = _run(self.pm.connect('123', '/')) self.pm.pre_disconnect(sid, '/') _run(self.pm.disconnect(sid, '/', ignore_queue=True)) - self.pm._publish.mock.assert_not_called() + self.pm._publish.assert_not_awaited() assert self.pm.is_connected(sid, '/') is False def test_enter_room(self): @@ -200,7 +200,7 @@ def test_enter_room(self): _run(self.pm.enter_room('456', '/', 'foo')) assert sid in self.pm.rooms['/']['foo'] assert self.pm.rooms['/']['foo'][sid] == '123' - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( {'method': 'enter_room', 'sid': '456', 'room': 'foo', 'namespace': '/', 'host_id': '123456'} ) @@ -210,32 +210,31 @@ def test_leave_room(self): _run(self.pm.leave_room(sid, '/', 'foo')) _run(self.pm.leave_room('456', '/', 'foo')) assert 'foo' not in self.pm.rooms['/'] - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( {'method': 'leave_room', 'sid': '456', 'room': 'foo', 'namespace': '/', 'host_id': '123456'} ) def test_close_room(self): _run(self.pm.close_room('foo')) - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( {'method': 'close_room', 'room': 'foo', 'namespace': '/', 'host_id': '123456'} ) def test_close_room_with_namespace(self): _run(self.pm.close_room('foo', '/bar')) - self.pm._publish.mock.assert_called_once_with( + self.pm._publish.assert_awaited_once_with( {'method': 'close_room', 'room': 'foo', 'namespace': '/bar', 'host_id': '123456'} ) def test_handle_emit(self): with mock.patch.object( - async_manager.AsyncManager, 'emit', new=AsyncMock() + async_manager.AsyncManager, 'emit' ) as super_emit: _run(self.pm._handle_emit({'event': 'foo', 'data': 'bar'})) - super_emit.mock.assert_called_once_with( - self.pm, + super_emit.assert_awaited_once_with( 'foo', 'bar', namespace=None, @@ -246,15 +245,14 @@ def test_handle_emit(self): def test_handle_emit_with_namespace(self): with mock.patch.object( - async_manager.AsyncManager, 'emit', new=AsyncMock() + async_manager.AsyncManager, 'emit' ) as super_emit: _run( self.pm._handle_emit( {'event': 'foo', 'data': 'bar', 'namespace': '/baz'} ) ) - super_emit.mock.assert_called_once_with( - self.pm, + super_emit.assert_awaited_once_with( 'foo', 'bar', namespace='/baz', @@ -265,15 +263,14 @@ def test_handle_emit_with_namespace(self): def test_handle_emit_with_room(self): with mock.patch.object( - async_manager.AsyncManager, 'emit', new=AsyncMock() + async_manager.AsyncManager, 'emit' ) as super_emit: _run( self.pm._handle_emit( {'event': 'foo', 'data': 'bar', 'room': 'baz'} ) ) - super_emit.mock.assert_called_once_with( - self.pm, + super_emit.assert_awaited_once_with( 'foo', 'bar', namespace=None, @@ -284,15 +281,14 @@ def test_handle_emit_with_room(self): def test_handle_emit_with_skip_sid(self): with mock.patch.object( - async_manager.AsyncManager, 'emit', new=AsyncMock() + async_manager.AsyncManager, 'emit' ) as super_emit: _run( self.pm._handle_emit( {'event': 'foo', 'data': 'bar', 'skip_sid': '123'} ) ) - super_emit.mock.assert_called_once_with( - self.pm, + super_emit.assert_awaited_once_with( 'foo', 'bar', namespace=None, @@ -303,7 +299,7 @@ def test_handle_emit_with_skip_sid(self): def test_handle_emit_with_remote_callback(self): with mock.patch.object( - async_manager.AsyncManager, 'emit', new=AsyncMock() + async_manager.AsyncManager, 'emit' ) as super_emit: _run( self.pm._handle_emit( @@ -316,16 +312,16 @@ def test_handle_emit_with_remote_callback(self): } ) ) - assert super_emit.mock.call_count == 1 - assert super_emit.mock.call_args[0] == (self.pm, 'foo', 'bar') - assert super_emit.mock.call_args[1]['namespace'] == '/baz' - assert super_emit.mock.call_args[1]['room'] is None - assert super_emit.mock.call_args[1]['skip_sid'] is None + assert super_emit.await_count == 1 + assert super_emit.await_args[0] == ('foo', 'bar') + assert super_emit.await_args[1]['namespace'] == '/baz' + assert super_emit.await_args[1]['room'] is None + assert super_emit.await_args[1]['skip_sid'] is None assert isinstance( - super_emit.mock.call_args[1]['callback'], functools.partial + super_emit.await_args[1]['callback'], functools.partial ) - _run(super_emit.mock.call_args[1]['callback']('one', 2, 'three')) - self.pm._publish.mock.assert_called_once_with( + _run(super_emit.await_args[1]['callback']('one', 2, 'three')) + self.pm._publish.assert_awaited_once_with( { 'method': 'callback', 'host_id': 'x', @@ -338,7 +334,7 @@ def test_handle_emit_with_remote_callback(self): def test_handle_emit_with_local_callback(self): with mock.patch.object( - async_manager.AsyncManager, 'emit', new=AsyncMock() + async_manager.AsyncManager, 'emit' ) as super_emit: _run( self.pm._handle_emit( @@ -351,21 +347,21 @@ def test_handle_emit_with_local_callback(self): } ) ) - assert super_emit.mock.call_count == 1 - assert super_emit.mock.call_args[0] == (self.pm, 'foo', 'bar') - assert super_emit.mock.call_args[1]['namespace'] == '/baz' - assert super_emit.mock.call_args[1]['room'] is None - assert super_emit.mock.call_args[1]['skip_sid'] is None + assert super_emit.await_count == 1 + assert super_emit.await_args[0] == ('foo', 'bar') + assert super_emit.await_args[1]['namespace'] == '/baz' + assert super_emit.await_args[1]['room'] is None + assert super_emit.await_args[1]['skip_sid'] is None assert isinstance( - super_emit.mock.call_args[1]['callback'], functools.partial + super_emit.await_args[1]['callback'], functools.partial ) - _run(super_emit.mock.call_args[1]['callback']('one', 2, 'three')) - self.pm._publish.mock.assert_not_called() + _run(super_emit.await_args[1]['callback']('one', 2, 'three')) + self.pm._publish.assert_not_awaited() def test_handle_callback(self): host_id = self.pm.host_id with mock.patch.object( - self.pm, 'trigger_callback', new=AsyncMock() + self.pm, 'trigger_callback' ) as trigger: _run( self.pm._handle_callback( @@ -379,11 +375,11 @@ def test_handle_callback(self): } ) ) - trigger.mock.assert_called_once_with('sid', 123, ('one', 2)) + trigger.assert_awaited_once_with('sid', 123, ('one', 2)) def test_handle_callback_bad_host_id(self): with mock.patch.object( - self.pm, 'trigger_callback', new=AsyncMock() + self.pm, 'trigger_callback' ) as trigger: _run( self.pm._handle_callback( @@ -397,12 +393,12 @@ def test_handle_callback_bad_host_id(self): } ) ) - assert trigger.mock.call_count == 0 + assert trigger.await_count == 0 def test_handle_callback_missing_args(self): host_id = self.pm.host_id with mock.patch.object( - self.pm, 'trigger_callback', new=AsyncMock() + self.pm, 'trigger_callback' ) as trigger: _run( self.pm._handle_callback( @@ -435,7 +431,7 @@ def test_handle_callback_missing_args(self): {'method': 'callback', 'host_id': host_id} ) ) - assert trigger.mock.call_count == 0 + assert trigger.await_count == 0 def test_handle_disconnect(self): _run( @@ -443,14 +439,14 @@ def test_handle_disconnect(self): {'method': 'disconnect', 'sid': '123', 'namespace': '/foo'} ) ) - self.pm.server.disconnect.mock.assert_called_once_with( + self.pm.server.disconnect.assert_awaited_once_with( sid='123', namespace='/foo', ignore_queue=True ) def test_handle_enter_room(self): sid = _run(self.pm.connect('123', '/')) with mock.patch.object( - async_manager.AsyncManager, 'enter_room', new=AsyncMock() + async_manager.AsyncManager, 'enter_room' ) as super_enter_room: _run( self.pm._handle_enter_room( @@ -464,14 +460,12 @@ def test_handle_enter_room(self): 'room': 'foo'} ) ) - super_enter_room.mock.assert_called_once_with( - self.pm, sid, '/', 'foo' - ) + super_enter_room.assert_awaited_once_with(sid, '/', 'foo') def test_handle_leave_room(self): sid = _run(self.pm.connect('123', '/')) with mock.patch.object( - async_manager.AsyncManager, 'leave_room', new=AsyncMock() + async_manager.AsyncManager, 'leave_room' ) as super_leave_room: _run( self.pm._handle_leave_room( @@ -485,26 +479,24 @@ def test_handle_leave_room(self): 'room': 'foo'} ) ) - super_leave_room.mock.assert_called_once_with( - self.pm, sid, '/', 'foo' - ) + super_leave_room.assert_awaited_once_with(sid, '/', 'foo') def test_handle_close_room(self): with mock.patch.object( - async_manager.AsyncManager, 'close_room', new=AsyncMock() + async_manager.AsyncManager, 'close_room' ) as super_close_room: _run( self.pm._handle_close_room( {'method': 'close_room', 'room': 'foo'} ) ) - super_close_room.mock.assert_called_once_with( - self.pm, room='foo', namespace=None + super_close_room.assert_awaited_once_with( + room='foo', namespace=None ) def test_handle_close_room_with_namespace(self): with mock.patch.object( - async_manager.AsyncManager, 'close_room', new=AsyncMock() + async_manager.AsyncManager, 'close_room' ) as super_close_room: _run( self.pm._handle_close_room( @@ -515,17 +507,17 @@ def test_handle_close_room_with_namespace(self): } ) ) - super_close_room.mock.assert_called_once_with( - self.pm, room='foo', namespace='/bar' + super_close_room.assert_awaited_once_with( + room='foo', namespace='/bar' ) def test_background_thread(self): - self.pm._handle_emit = AsyncMock() - self.pm._handle_callback = AsyncMock() - self.pm._handle_disconnect = AsyncMock() - self.pm._handle_enter_room = AsyncMock() - self.pm._handle_leave_room = AsyncMock() - self.pm._handle_close_room = AsyncMock() + self.pm._handle_emit = mock.AsyncMock() + self.pm._handle_callback = mock.AsyncMock() + self.pm._handle_disconnect = mock.AsyncMock() + self.pm._handle_enter_room = mock.AsyncMock() + self.pm._handle_leave_room = mock.AsyncMock() + self.pm._handle_close_room = mock.AsyncMock() host_id = self.pm.host_id async def messages(): @@ -558,34 +550,34 @@ async def messages(): self.pm._listen = messages _run(self.pm._thread()) - self.pm._handle_emit.mock.assert_called_once_with( + self.pm._handle_emit.assert_awaited_once_with( {'method': 'emit', 'value': 'foo', 'host_id': 'x'} ) - self.pm._handle_callback.mock.assert_any_call( + self.pm._handle_callback.assert_any_await( {'method': 'callback', 'value': 'bar', 'host_id': 'x'} ) - self.pm._handle_callback.mock.assert_any_call( + self.pm._handle_callback.assert_any_await( {'method': 'callback', 'value': 'bar', 'host_id': host_id} ) - self.pm._handle_disconnect.mock.assert_called_once_with( + self.pm._handle_disconnect.assert_awaited_once_with( {'method': 'disconnect', 'sid': '123', 'namespace': '/foo', 'host_id': 'x'} ) - self.pm._handle_enter_room.mock.assert_called_once_with( + self.pm._handle_enter_room.assert_awaited_once_with( {'method': 'enter_room', 'sid': '123', 'namespace': '/foo', 'room': 'room', 'host_id': 'x'} ) - self.pm._handle_leave_room.mock.assert_called_once_with( + self.pm._handle_leave_room.assert_awaited_once_with( {'method': 'leave_room', 'sid': '123', 'namespace': '/foo', 'room': 'room', 'host_id': 'x'} ) - self.pm._handle_close_room.mock.assert_called_once_with( + self.pm._handle_close_room.assert_awaited_once_with( {'method': 'close_room', 'value': 'baz', 'host_id': 'x'} ) def test_background_thread_exception(self): - self.pm._handle_emit = AsyncMock(side_effect=[ValueError(), - asyncio.CancelledError]) + self.pm._handle_emit = mock.AsyncMock(side_effect=[ + ValueError(), asyncio.CancelledError]) async def messages(): yield {'method': 'emit', 'value': 'foo', 'host_id': 'x'} @@ -594,9 +586,9 @@ async def messages(): self.pm._listen = messages _run(self.pm._thread()) - self.pm._handle_emit.mock.assert_any_call( + self.pm._handle_emit.assert_any_await( {'method': 'emit', 'value': 'foo', 'host_id': 'x'} ) - self.pm._handle_emit.mock.assert_called_with( + self.pm._handle_emit.assert_awaited_with( {'method': 'emit', 'value': 'bar', 'host_id': 'x'} ) diff --git a/tests/async/test_server.py b/tests/async/test_server.py index 8b2dfe28..704982bd 100644 --- a/tests/async/test_server.py +++ b/tests/async/test_server.py @@ -11,12 +11,12 @@ from socketio import exceptions from socketio import namespace from socketio import packet -from .helpers import AsyncMock, _run +from .helpers import _run @mock.patch('socketio.server.engineio.AsyncServer', **{ 'return_value.generate_id.side_effect': [str(i) for i in range(1, 10)], - 'return_value.send_packet': AsyncMock()}) + 'return_value.send_packet': mock.AsyncMock()}) class TestAsyncServer: def teardown_method(self): # restore JSON encoder, in case a test changed it @@ -24,16 +24,16 @@ def teardown_method(self): def _get_mock_manager(self): mgr = mock.MagicMock() - mgr.can_disconnect = AsyncMock() - mgr.emit = AsyncMock() - mgr.enter_room = AsyncMock() - mgr.leave_room = AsyncMock() - mgr.close_room = AsyncMock() - mgr.trigger_callback = AsyncMock() + mgr.can_disconnect = mock.AsyncMock() + mgr.emit = mock.AsyncMock() + mgr.enter_room = mock.AsyncMock() + mgr.leave_room = mock.AsyncMock() + mgr.close_room = mock.AsyncMock() + mgr.trigger_callback = mock.AsyncMock() return mgr def test_create(self, eio): - eio.return_value.handle_request = AsyncMock() + eio.return_value.handle_request = mock.AsyncMock() mgr = self._get_mock_manager() s = async_server.AsyncServer( client_manager=mgr, async_handlers=True, foo='bar' @@ -80,7 +80,7 @@ def test_emit(self, eio): callback='cb', ) ) - s.manager.emit.mock.assert_called_once_with( + s.manager.emit.assert_awaited_once_with( 'my event', {'foo': 'bar'}, '/foo', @@ -100,7 +100,7 @@ def test_emit(self, eio): ignore_queue=True, ) ) - s.manager.emit.mock.assert_called_with( + s.manager.emit.assert_awaited_with( 'my event', {'foo': 'bar'}, '/foo', @@ -122,7 +122,7 @@ def test_emit_default_namespace(self, eio): callback='cb', ) ) - s.manager.emit.mock.assert_called_once_with( + s.manager.emit.assert_awaited_once_with( 'my event', {'foo': 'bar'}, '/', @@ -141,7 +141,7 @@ def test_emit_default_namespace(self, eio): ignore_queue=True, ) ) - s.manager.emit.mock.assert_called_with( + s.manager.emit.assert_awaited_with( 'my event', {'foo': 'bar'}, '/', @@ -163,7 +163,7 @@ def test_send(self, eio): callback='cb', ) ) - s.manager.emit.mock.assert_called_once_with( + s.manager.emit.assert_awaited_once_with( 'message', 'foo', '/foo', @@ -182,7 +182,7 @@ def test_send(self, eio): ignore_queue=True, ) ) - s.manager.emit.mock.assert_called_with( + s.manager.emit.assert_awaited_with( 'message', 'foo', '/foo', @@ -197,7 +197,7 @@ def test_call(self, eio): s = async_server.AsyncServer(client_manager=mgr) async def fake_event_wait(): - s.manager.emit.mock.call_args_list[0][1]['callback']('foo', 321) + s.manager.emit.await_args_list[0][1]['callback']('foo', 321) return True s.eio.create_event.return_value.wait = fake_event_wait @@ -231,39 +231,37 @@ def test_enter_room(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) _run(s.enter_room('123', 'room', namespace='/foo')) - s.manager.enter_room.mock.assert_called_once_with('123', '/foo', - 'room') + s.manager.enter_room.assert_awaited_once_with('123', '/foo', 'room') def test_enter_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) _run(s.enter_room('123', 'room')) - s.manager.enter_room.mock.assert_called_once_with('123', '/', 'room') + s.manager.enter_room.assert_awaited_once_with('123', '/', 'room') def test_leave_room(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) _run(s.leave_room('123', 'room', namespace='/foo')) - s.manager.leave_room.mock.assert_called_once_with('123', '/foo', - 'room') + s.manager.leave_room.assert_awaited_once_with('123', '/foo', 'room') def test_leave_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) _run(s.leave_room('123', 'room')) - s.manager.leave_room.mock.assert_called_once_with('123', '/', 'room') + s.manager.leave_room.assert_awaited_once_with('123', '/', 'room') def test_close_room(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) _run(s.close_room('room', namespace='/foo')) - s.manager.close_room.mock.assert_called_once_with('room', '/foo') + s.manager.close_room.assert_awaited_once_with('room', '/foo') def test_close_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) _run(s.close_room('room')) - s.manager.close_room.mock.assert_called_once_with('room', '/') + s.manager.close_room.assert_awaited_once_with('room', '/') def test_rooms(self, eio): mgr = self._get_mock_manager() @@ -278,32 +276,32 @@ def test_rooms_default_namespace(self, eio): s.manager.get_rooms.assert_called_once_with('123', '/') def test_handle_request(self, eio): - eio.return_value.handle_request = AsyncMock() + eio.return_value.handle_request = mock.AsyncMock() s = async_server.AsyncServer() _run(s.handle_request('environ')) - s.eio.handle_request.mock.assert_called_once_with('environ') + s.eio.handle_request.assert_awaited_once_with('environ') def test_send_packet(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() _run(s._send_packet('123', packet.Packet( packet.EVENT, ['my event', 'my data'], namespace='/foo'))) - s.eio.send.mock.assert_called_once_with( + s.eio.send.assert_awaited_once_with( '123', '2/foo,["my event","my data"]' ) def test_send_eio_packet(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() _run(s._send_eio_packet('123', eio_packet.Packet( eio_packet.MESSAGE, 'hello'))) - assert s.eio.send_packet.mock.call_count == 1 - assert s.eio.send_packet.mock.call_args_list[0][0][0] == '123' - pkt = s.eio.send_packet.mock.call_args_list[0][0][1] + assert s.eio.send_packet.await_count == 1 + assert s.eio.send_packet.await_args_list[0][0][0] == '123' + pkt = s.eio.send_packet.await_args_list[0][0][1] assert pkt.encode() == '4hello' def test_transport(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.eio.transport = mock.MagicMock(return_value='polling') sid_foo = _run(s.manager.connect('123', '/foo')) @@ -311,7 +309,7 @@ def test_transport(self, eio): s.eio.transport.assert_called_once_with('123') def test_handle_connect(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock() @@ -320,14 +318,14 @@ def test_handle_connect(self, eio): _run(s._handle_eio_message('123', '0')) assert s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_called_once_with('123', '0{"sid":"1"}') + s.eio.send.assert_awaited_once_with('123', '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 _run(s._handle_eio_connect('456', 'environ')) _run(s._handle_eio_message('456', '0')) assert s.manager.initialize.call_count == 1 def test_handle_connect_with_auth(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock() @@ -336,14 +334,14 @@ def test_handle_connect_with_auth(self, eio): _run(s._handle_eio_message('123', '0{"token":"abc"}')) assert s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ', {'token': 'abc'}) - s.eio.send.mock.assert_called_once_with('123', '0{"sid":"1"}') + s.eio.send.assert_awaited_once_with('123', '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 _run(s._handle_eio_connect('456', 'environ')) _run(s._handle_eio_message('456', '0')) assert s.manager.initialize.call_count == 1 def test_handle_connect_with_auth_none(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock(side_effect=[TypeError, None, None]) @@ -352,30 +350,30 @@ def test_handle_connect_with_auth_none(self, eio): _run(s._handle_eio_message('123', '0')) assert s.manager.is_connected('1', '/') handler.assert_called_with('1', 'environ', None) - s.eio.send.mock.assert_called_once_with('123', '0{"sid":"1"}') + s.eio.send.assert_awaited_once_with('123', '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 _run(s._handle_eio_connect('456', 'environ')) _run(s._handle_eio_message('456', '0')) assert s.manager.initialize.call_count == 1 def test_handle_connect_async(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() - handler = AsyncMock() + handler = mock.AsyncMock() s.on('connect', handler) _run(s._handle_eio_connect('123', 'environ')) _run(s._handle_eio_message('123', '0')) assert s.manager.is_connected('1', '/') - handler.mock.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_called_once_with('123', '0{"sid":"1"}') + handler.assert_awaited_once_with('1', 'environ') + s.eio.send.assert_awaited_once_with('123', '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 _run(s._handle_eio_connect('456', 'environ')) _run(s._handle_eio_message('456', '0')) assert s.manager.initialize.call_count == 1 def test_handle_connect_with_default_implied_namespaces(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() _run(s._handle_eio_connect('123', 'environ')) _run(s._handle_eio_message('123', '0')) @@ -384,7 +382,7 @@ def test_handle_connect_with_default_implied_namespaces(self, eio): assert not s.manager.is_connected('2', '/foo') def test_handle_connect_with_implied_namespaces(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(namespaces=['/foo']) _run(s._handle_eio_connect('123', 'environ')) _run(s._handle_eio_message('123', '0')) @@ -393,7 +391,7 @@ def test_handle_connect_with_implied_namespaces(self, eio): assert s.manager.is_connected('1', '/foo') def test_handle_connect_with_all_implied_namespaces(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(namespaces='*') _run(s._handle_eio_connect('123', 'environ')) _run(s._handle_eio_message('123', '0')) @@ -402,7 +400,7 @@ def test_handle_connect_with_all_implied_namespaces(self, eio): assert s.manager.is_connected('2', '/foo') def test_handle_connect_namespace(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock() s.on('connect', handler, namespace='/foo') @@ -410,10 +408,10 @@ def test_handle_connect_namespace(self, eio): _run(s._handle_eio_message('123', '0/foo,')) assert s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_called_once_with('123', '0/foo,{"sid":"1"}') + s.eio.send.assert_awaited_once_with('123', '0/foo,{"sid":"1"}') def test_handle_connect_always_connect(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(always_connect=True) s.manager.initialize = mock.MagicMock() handler = mock.MagicMock() @@ -422,14 +420,14 @@ def test_handle_connect_always_connect(self, eio): _run(s._handle_eio_message('123', '0')) assert s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_called_once_with('123', '0{"sid":"1"}') + s.eio.send.assert_awaited_once_with('123', '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 _run(s._handle_eio_connect('456', 'environ')) _run(s._handle_eio_message('456', '0')) assert s.manager.initialize.call_count == 1 def test_handle_connect_rejected(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock(return_value=False) s.on('connect', handler) @@ -437,12 +435,12 @@ def test_handle_connect_rejected(self, eio): _run(s._handle_eio_message('123', '0')) assert not s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_called_once_with( + s.eio.send.assert_awaited_once_with( '123', '4{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} def test_handle_connect_namespace_rejected(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock(return_value=False) s.on('connect', handler, namespace='/foo') @@ -450,12 +448,12 @@ def test_handle_connect_namespace_rejected(self, eio): _run(s._handle_eio_message('123', '0/foo,')) assert not s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_any_call( + s.eio.send.assert_any_await( '123', '4/foo,{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} def test_handle_connect_rejected_always_connect(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(always_connect=True) handler = mock.MagicMock(return_value=False) s.on('connect', handler) @@ -463,13 +461,13 @@ def test_handle_connect_rejected_always_connect(self, eio): _run(s._handle_eio_message('123', '0')) assert not s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_any_call('123', '0{"sid":"1"}') - s.eio.send.mock.assert_any_call( + s.eio.send.assert_any_await('123', '0{"sid":"1"}') + s.eio.send.assert_any_await( '123', '1{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} def test_handle_connect_namespace_rejected_always_connect(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(always_connect=True) handler = mock.MagicMock(return_value=False) s.on('connect', handler, namespace='/foo') @@ -477,13 +475,13 @@ def test_handle_connect_namespace_rejected_always_connect(self, eio): _run(s._handle_eio_message('123', '0/foo,')) assert not s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_any_call('123', '0/foo,{"sid":"1"}') - s.eio.send.mock.assert_any_call( + s.eio.send.assert_any_await('123', '0/foo,{"sid":"1"}') + s.eio.send.assert_any_await( '123', '1/foo,{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} def test_handle_connect_rejected_with_exception(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock( side_effect=exceptions.ConnectionRefusedError('fail_reason') @@ -493,12 +491,12 @@ def test_handle_connect_rejected_with_exception(self, eio): _run(s._handle_eio_message('123', '0')) assert not s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_called_once_with( + s.eio.send.assert_awaited_once_with( '123', '4{"message":"fail_reason"}') assert s.environ == {'123': 'environ'} def test_handle_connect_rejected_with_empty_exception(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock( side_effect=exceptions.ConnectionRefusedError() @@ -508,12 +506,12 @@ def test_handle_connect_rejected_with_empty_exception(self, eio): _run(s._handle_eio_message('123', '0')) assert not s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_called_once_with( + s.eio.send.assert_awaited_once_with( '123', '4{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} def test_handle_connect_namespace_rejected_with_exception(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock( side_effect=exceptions.ConnectionRefusedError( @@ -524,12 +522,12 @@ def test_handle_connect_namespace_rejected_with_exception(self, eio): _run(s._handle_eio_message('123', '0/foo,')) assert not s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_called_once_with( + s.eio.send.assert_awaited_once_with( '123', '4/foo,{"message":"fail_reason","data":[1,"2"]}') assert s.environ == {'123': 'environ'} def test_handle_connect_namespace_rejected_with_empty_exception(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock( side_effect=exceptions.ConnectionRefusedError() @@ -539,26 +537,26 @@ def test_handle_connect_namespace_rejected_with_empty_exception(self, eio): _run(s._handle_eio_message('123', '0/foo,')) assert not s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') - s.eio.send.mock.assert_called_once_with( + s.eio.send.assert_awaited_once_with( '123', '4/foo,{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} def test_handle_disconnect(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - s.manager.disconnect = AsyncMock() + s.manager.disconnect = mock.AsyncMock() handler = mock.MagicMock() s.on('disconnect', handler) _run(s._handle_eio_connect('123', 'environ')) _run(s._handle_eio_message('123', '0')) _run(s._handle_eio_disconnect('123')) handler.assert_called_once_with('1') - s.manager.disconnect.mock.assert_called_once_with( + s.manager.disconnect.assert_awaited_once_with( '1', '/', ignore_queue=True) assert s.environ == {} def test_handle_disconnect_namespace(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock() s.on('disconnect', handler) @@ -572,7 +570,7 @@ def test_handle_disconnect_namespace(self, eio): assert s.environ == {} def test_handle_disconnect_only_namespace(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock() s.on('disconnect', handler) @@ -591,21 +589,21 @@ def test_handle_disconnect_unknown_client(self, eio): _run(s._handle_eio_disconnect('123')) def test_handle_event(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) sid = _run(s.manager.connect('123', '/')) - handler = AsyncMock() - catchall_handler = AsyncMock() + handler = mock.AsyncMock() + catchall_handler = mock.AsyncMock() s.on('msg', handler) s.on('*', catchall_handler) _run(s._handle_eio_message('123', '2["msg","a","b"]')) _run(s._handle_eio_message('123', '2["my message","a","b","c"]')) - handler.mock.assert_called_once_with(sid, 'a', 'b') - catchall_handler.mock.assert_called_once_with( + handler.assert_awaited_once_with(sid, 'a', 'b') + catchall_handler.assert_awaited_once_with( 'my message', sid, 'a', 'b', 'c') def test_handle_event_with_namespace(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) sid = _run(s.manager.connect('123', '/foo')) handler = mock.MagicMock() @@ -619,7 +617,7 @@ def test_handle_event_with_namespace(self, eio): 'my message', sid, 'a', 'b', 'c') def test_handle_event_with_catchall_namespace(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) sid_foo = _run(s.manager.connect('123', '/foo')) sid_bar = _run(s.manager.connect('123', '/bar')) @@ -648,7 +646,7 @@ def test_handle_event_with_catchall_namespace(self, eio): 'my message', '/bar', sid_bar, 'a', 'b', 'c') def test_handle_event_with_disconnected_namespace(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) _run(s.manager.connect('123', '/foo')) handler = mock.MagicMock() @@ -657,7 +655,7 @@ def test_handle_event_with_disconnected_namespace(self, eio): handler.assert_not_called() def test_handle_event_binary(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) sid = _run(s.manager.connect('123', '/')) handler = mock.MagicMock() @@ -675,9 +673,9 @@ def test_handle_event_binary(self, eio): handler.assert_called_once_with(sid, 'a', b'bar', b'foo') def test_handle_event_binary_ack(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - s.manager.trigger_callback = AsyncMock() + s.manager.trigger_callback = mock.AsyncMock() sid = _run(s.manager.connect('123', '/')) _run( s._handle_eio_message( @@ -686,67 +684,67 @@ def test_handle_event_binary_ack(self, eio): ) ) _run(s._handle_eio_message('123', b'foo')) - s.manager.trigger_callback.mock.assert_called_once_with( + s.manager.trigger_callback.assert_awaited_once_with( sid, 321, ['my message', 'a', b'foo'] ) def test_handle_event_with_ack(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) sid = _run(s.manager.connect('123', '/')) handler = mock.MagicMock(return_value='foo') s.on('my message', handler) _run(s._handle_eio_message('123', '21000["my message","foo"]')) handler.assert_called_once_with(sid, 'foo') - s.eio.send.mock.assert_called_once_with( + s.eio.send.assert_awaited_once_with( '123', '31000["foo"]' ) def test_handle_unknown_event_with_ack(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) _run(s.manager.connect('123', '/')) handler = mock.MagicMock(return_value='foo') s.on('my message', handler) _run(s._handle_eio_message('123', '21000["another message","foo"]')) - s.eio.send.mock.assert_not_called() + s.eio.send.assert_not_awaited() def test_handle_event_with_ack_none(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) sid = _run(s.manager.connect('123', '/')) handler = mock.MagicMock(return_value=None) s.on('my message', handler) _run(s._handle_eio_message('123', '21000["my message","foo"]')) handler.assert_called_once_with(sid, 'foo') - s.eio.send.mock.assert_called_once_with('123', '31000[]') + s.eio.send.assert_awaited_once_with('123', '31000[]') def test_handle_event_with_ack_tuple(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) sid = _run(s.manager.connect('123', '/')) handler = mock.MagicMock(return_value=(1, '2', True)) s.on('my message', handler) _run(s._handle_eio_message('123', '21000["my message","a","b","c"]')) handler.assert_called_once_with(sid, 'a', 'b', 'c') - s.eio.send.mock.assert_called_once_with( + s.eio.send.assert_awaited_once_with( '123', '31000[1,"2",true]' ) def test_handle_event_with_ack_list(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) sid = _run(s.manager.connect('123', '/')) handler = mock.MagicMock(return_value=[1, '2', True]) s.on('my message', handler) _run(s._handle_eio_message('123', '21000["my message","a","b","c"]')) handler.assert_called_once_with(sid, 'a', 'b', 'c') - s.eio.send.mock.assert_called_once_with( + s.eio.send.assert_awaited_once_with( '123', '31000[[1,"2",true]]' ) def test_handle_event_with_ack_binary(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) sid = _run(s.manager.connect('123', '/')) handler = mock.MagicMock(return_value=b'foo') @@ -765,7 +763,7 @@ def test_handle_invalid_packet(self, eio): _run(s._handle_eio_message('123', '9')) def test_send_with_ack(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/'] = {} _run(s._handle_eio_connect('123', 'environ')) @@ -781,7 +779,7 @@ def test_send_with_ack(self, eio): cb.assert_called_once_with('foo', 2) def test_send_with_ack_namespace(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/foo'] = {} _run(s._handle_eio_connect('123', 'environ')) @@ -809,7 +807,7 @@ async def fake_save_session(eio_sid, session): assert eio_sid == '123' fake_session = session - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/'] = {} s.handlers['/ns'] = {} @@ -841,63 +839,63 @@ async def _test(): _run(_test()) def test_disconnect(self, eio): - eio.return_value.send = AsyncMock() - eio.return_value.disconnect = AsyncMock() + eio.return_value.send = mock.AsyncMock() + eio.return_value.disconnect = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/'] = {} _run(s._handle_eio_connect('123', 'environ')) _run(s._handle_eio_message('123', '0')) _run(s.disconnect('1')) - s.eio.send.mock.assert_any_call('123', '1') + s.eio.send.assert_any_await('123', '1') assert not s.manager.is_connected('1', '/') def test_disconnect_ignore_queue(self, eio): - eio.return_value.send = AsyncMock() - eio.return_value.disconnect = AsyncMock() + eio.return_value.send = mock.AsyncMock() + eio.return_value.disconnect = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/'] = {} _run(s._handle_eio_connect('123', 'environ')) _run(s._handle_eio_message('123', '0')) _run(s.disconnect('1', ignore_queue=True)) - s.eio.send.mock.assert_any_call('123', '1') + s.eio.send.assert_any_await('123', '1') assert not s.manager.is_connected('1', '/') def test_disconnect_namespace(self, eio): - eio.return_value.send = AsyncMock() - eio.return_value.disconnect = AsyncMock() + eio.return_value.send = mock.AsyncMock() + eio.return_value.disconnect = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/foo'] = {} _run(s._handle_eio_connect('123', 'environ')) _run(s._handle_eio_message('123', '0/foo,')) _run(s.disconnect('1', namespace='/foo')) - s.eio.send.mock.assert_any_call('123', '1/foo,') + s.eio.send.assert_any_await('123', '1/foo,') assert not s.manager.is_connected('1', '/foo') def test_disconnect_twice(self, eio): - eio.return_value.send = AsyncMock() - eio.return_value.disconnect = AsyncMock() + eio.return_value.send = mock.AsyncMock() + eio.return_value.disconnect = mock.AsyncMock() s = async_server.AsyncServer() _run(s._handle_eio_connect('123', 'environ')) _run(s._handle_eio_message('123', '0')) _run(s.disconnect('1')) - calls = s.eio.send.mock.call_count + calls = s.eio.send.await_count assert not s.manager.is_connected('1', '/') _run(s.disconnect('1')) - assert calls == s.eio.send.mock.call_count + assert calls == s.eio.send.await_count def test_disconnect_twice_namespace(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() _run(s._handle_eio_connect('123', 'environ')) _run(s._handle_eio_message('123', '0/foo,')) _run(s.disconnect('1', namespace='/foo')) - calls = s.eio.send.mock.call_count + calls = s.eio.send.await_count assert not s.manager.is_connected('1', '/foo') _run(s.disconnect('1', namespace='/foo')) - assert calls == s.eio.send.mock.call_count + assert calls == s.eio.send.await_count def test_namespace_handler(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() result = {} class MyNamespace(async_namespace.AsyncNamespace): @@ -931,7 +929,7 @@ async def on_baz(self, sid, data1, data2): assert result['result'] == ('disconnect', '1') def test_catchall_namespace_handler(self, eio): - eio.return_value.send = AsyncMock() + eio.return_value.send = mock.AsyncMock() result = {} class MyNamespace(async_namespace.AsyncNamespace): @@ -1047,9 +1045,9 @@ def test_async_handlers(self, eio): def test_shutdown(self, eio): s = async_server.AsyncServer() - s.eio.shutdown = AsyncMock() + s.eio.shutdown = mock.AsyncMock() _run(s.shutdown()) - s.eio.shutdown.mock.assert_called_once_with() + s.eio.shutdown.assert_awaited_once_with() def test_start_background_task(self, eio): s = async_server.AsyncServer() @@ -1059,7 +1057,7 @@ def test_start_background_task(self, eio): ) def test_sleep(self, eio): - eio.return_value.sleep = AsyncMock() + eio.return_value.sleep = mock.AsyncMock() s = async_server.AsyncServer() _run(s.sleep(1.23)) - s.eio.sleep.mock.assert_called_once_with(1.23) + s.eio.sleep.assert_awaited_once_with(1.23) diff --git a/tests/async/test_simple_client.py b/tests/async/test_simple_client.py index 6a2eb7ad..a8d08f41 100644 --- a/tests/async/test_simple_client.py +++ b/tests/async/test_simple_client.py @@ -4,7 +4,7 @@ from socketio import AsyncSimpleClient from socketio.exceptions import SocketIOError, TimeoutError, DisconnectedError -from .helpers import AsyncMock, _run +from .helpers import _run class TestAsyncAsyncSimpleClient: @@ -20,14 +20,14 @@ def test_connect(self): client = AsyncSimpleClient(123, a='b') with mock.patch('socketio.async_simple_client.AsyncClient') \ as mock_client: - mock_client.return_value.connect = AsyncMock() + mock_client.return_value.connect = mock.AsyncMock() _run(client.connect('url', headers='h', auth='a', transports='t', namespace='n', socketio_path='s', wait_timeout='w')) mock_client.assert_called_once_with(123, a='b') assert client.client == mock_client() - mock_client().connect.mock.assert_called_once_with( + mock_client().connect.assert_awaited_once_with( 'url', headers='h', auth='a', transports='t', namespaces=['n'], socketio_path='s', wait_timeout='w') mock_client().event.call_count == 3 @@ -40,14 +40,14 @@ async def _t(): async with AsyncSimpleClient(123, a='b') as client: with mock.patch('socketio.async_simple_client.AsyncClient') \ as mock_client: - mock_client.return_value.connect = AsyncMock() + mock_client.return_value.connect = mock.AsyncMock() await client.connect('url', headers='h', auth='a', transports='t', namespace='n', socketio_path='s', wait_timeout='w') mock_client.assert_called_once_with(123, a='b') assert client.client == mock_client() - mock_client().connect.mock.assert_called_once_with( + mock_client().connect.assert_awaited_once_with( 'url', headers='h', auth='a', transports='t', namespaces=['n'], socketio_path='s', wait_timeout='w') mock_client().event.call_count == 3 @@ -79,14 +79,14 @@ def test_properties(self): def test_emit(self): client = AsyncSimpleClient() client.client = mock.MagicMock() - client.client.emit = AsyncMock() + client.client.emit = mock.AsyncMock() client.namespace = '/ns' client.connected_event.set() client.connected = True _run(client.emit('foo', 'bar')) - client.client.emit.mock.assert_called_once_with('foo', 'bar', - namespace='/ns') + client.client.emit.assert_awaited_once_with('foo', 'bar', + namespace='/ns') def test_emit_disconnected(self): client = AsyncSimpleClient() @@ -100,23 +100,23 @@ def test_emit_retries(self): client.connected_event.set() client.connected = True client.client = mock.MagicMock() - client.client.emit = AsyncMock() - client.client.emit.mock.side_effect = [SocketIOError(), None] + client.client.emit = mock.AsyncMock() + client.client.emit.side_effect = [SocketIOError(), None] _run(client.emit('foo', 'bar')) - client.client.emit.mock.assert_called_with('foo', 'bar', namespace='/') + client.client.emit.assert_awaited_with('foo', 'bar', namespace='/') def test_call(self): client = AsyncSimpleClient() client.client = mock.MagicMock() - client.client.call = AsyncMock() - client.client.call.mock.return_value = 'result' + client.client.call = mock.AsyncMock() + client.client.call.return_value = 'result' client.namespace = '/ns' client.connected_event.set() client.connected = True assert _run(client.call('foo', 'bar')) == 'result' - client.client.call.mock.assert_called_once_with( + client.client.call.assert_awaited_once_with( 'foo', 'bar', namespace='/ns', timeout=60) def test_call_disconnected(self): @@ -131,12 +131,12 @@ def test_call_retries(self): client.connected_event.set() client.connected = True client.client = mock.MagicMock() - client.client.call = AsyncMock() - client.client.call.mock.side_effect = [SocketIOError(), 'result'] + client.client.call = mock.AsyncMock() + client.client.call.side_effect = [SocketIOError(), 'result'] assert _run(client.call('foo', 'bar')) == 'result' - client.client.call.mock.assert_called_with('foo', 'bar', namespace='/', - timeout=60) + client.client.call.assert_awaited_with('foo', 'bar', namespace='/', + timeout=60) def test_receive_with_input_buffer(self): client = AsyncSimpleClient() @@ -180,10 +180,10 @@ def test_receive_disconnected(self): def test_disconnect(self): client = AsyncSimpleClient() mc = mock.MagicMock() - mc.disconnect = AsyncMock() + mc.disconnect = mock.AsyncMock() client.client = mc client.connected = True _run(client.disconnect()) _run(client.disconnect()) - mc.disconnect.mock.assert_called_once_with() + mc.disconnect.assert_awaited_once_with() assert client.client is None From 3e95bb5ca8711dcd9e6b4cb077576c67980f7bba Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 17:25:49 +0000 Subject: [PATCH 080/173] Bump path-to-regexp and express in /examples/client/javascript (#1416) #nolog Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) to 0.1.12 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `path-to-regexp` from 0.1.10 to 0.1.12 - [Release notes](https://github.com/pillarjs/path-to-regexp/releases) - [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md) - [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.10...v0.1.12) Updates `express` from 4.21.1 to 4.21.2 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.1...4.21.2) --- updated-dependencies: - dependency-name: path-to-regexp dependency-type: indirect - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/client/javascript/package-lock.json | 34 +++++++++++--------- examples/client/javascript/package.json | 2 +- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/client/javascript/package-lock.json b/examples/client/javascript/package-lock.json index ca02f7af..5a2ec0ca 100644 --- a/examples/client/javascript/package-lock.json +++ b/examples/client/javascript/package-lock.json @@ -8,7 +8,7 @@ "name": "socketio-examples", "version": "0.1.0", "dependencies": { - "express": "^4.21.1", + "express": "^4.21.2", "smoothie": "1.19.0", "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" @@ -335,9 +335,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -358,7 +358,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -373,6 +373,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/encodeurl": { @@ -650,9 +654,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -1286,9 +1290,9 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -1309,7 +1313,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -1509,9 +1513,9 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "proxy-addr": { "version": "2.0.7", diff --git a/examples/client/javascript/package.json b/examples/client/javascript/package.json index 6e806da3..6bea0d87 100644 --- a/examples/client/javascript/package.json +++ b/examples/client/javascript/package.json @@ -2,7 +2,7 @@ "name": "socketio-examples", "version": "0.1.0", "dependencies": { - "express": "^4.21.1", + "express": "^4.21.2", "smoothie": "1.19.0", "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" From f5b686c1044d3e9a0fe54d7270f0963dbd21f486 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Dec 2024 00:16:45 +0000 Subject: [PATCH 081/173] Bump django in /examples/server/wsgi/django_socketio (#1417) #nolog Bumps [django](https://github.com/django/django) from 4.2.16 to 4.2.17. - [Commits](https://github.com/django/django/compare/4.2.16...4.2.17) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/django_socketio/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/django_socketio/requirements.txt b/examples/server/wsgi/django_socketio/requirements.txt index 53a28118..98d2cf1f 100644 --- a/examples/server/wsgi/django_socketio/requirements.txt +++ b/examples/server/wsgi/django_socketio/requirements.txt @@ -1,6 +1,6 @@ asgiref==3.6.0 bidict==0.22.1 -Django==4.2.16 +Django==4.2.17 gunicorn==22.0.0 h11==0.14.0 python-engineio From b75d7309b75936017bc45eebfd5093b96f132e69 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Dec 2024 11:02:49 +0000 Subject: [PATCH 082/173] Bump path-to-regexp and express in /examples/server/javascript (#1418) #nolog Bumps [path-to-regexp](https://github.com/pillarjs/path-to-regexp) to 0.1.12 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `path-to-regexp` from 0.1.10 to 0.1.12 - [Release notes](https://github.com/pillarjs/path-to-regexp/releases) - [Changelog](https://github.com/pillarjs/path-to-regexp/blob/master/History.md) - [Commits](https://github.com/pillarjs/path-to-regexp/compare/v0.1.10...v0.1.12) Updates `express` from 4.21.1 to 4.21.2 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/4.21.2/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.1...4.21.2) --- updated-dependencies: - dependency-name: path-to-regexp dependency-type: indirect - dependency-name: express dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/javascript/package-lock.json | 34 +++++++++++--------- examples/server/javascript/package.json | 2 +- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/examples/server/javascript/package-lock.json b/examples/server/javascript/package-lock.json index befb6751..4bae767a 100644 --- a/examples/server/javascript/package-lock.json +++ b/examples/server/javascript/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@socket.io/admin-ui": "^0.5.1", - "express": "^4.21.1", + "express": "^4.21.2", "smoothie": "1.19.0", "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" @@ -338,9 +338,9 @@ } }, "node_modules/express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -361,7 +361,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -376,6 +376,10 @@ }, "engines": { "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/express/node_modules/debug": { @@ -679,9 +683,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -1251,9 +1255,9 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", @@ -1274,7 +1278,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -1500,9 +1504,9 @@ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" }, "path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, "proxy-addr": { "version": "2.0.7", diff --git a/examples/server/javascript/package.json b/examples/server/javascript/package.json index b93cdb63..1e0e150c 100644 --- a/examples/server/javascript/package.json +++ b/examples/server/javascript/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "dependencies": { "@socket.io/admin-ui": "^0.5.1", - "express": "^4.21.1", + "express": "^4.21.2", "smoothie": "1.19.0", "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" From bf5a05ae9bf94b2fbd1367a04884ef5a39cd2671 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 12 Dec 2024 01:54:15 -0800 Subject: [PATCH 083/173] server.py: teeny docstring typo fix (#1421) Noticed while perusing the documentation, so submitting a micropatch. --- src/socketio/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socketio/server.py b/src/socketio/server.py index a40dcd90..883b37ab 100644 --- a/src/socketio/server.py +++ b/src/socketio/server.py @@ -87,7 +87,7 @@ class Server(base_server.BaseServer): is greater than this value. The default is 1024 bytes. :param cookie: If set to a string, it is the name of the HTTP cookie the - server sends back tot he client containing the client + server sends back to the client containing the client session id. If set to a dictionary, the ``'name'`` key contains the cookie name and other keys define cookie attributes, where the value of each attribute can be a From db642bb2bd9794eeceddd54abc47665c69e85406 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Thu, 12 Dec 2024 19:23:05 +0000 Subject: [PATCH 084/173] Upgrade the code to more recent Python versions --- examples/client/async/latency_client.py | 2 +- examples/client/sync/latency_client.py | 2 +- examples/simple-client/async/latency_client.py | 2 +- examples/simple-client/sync/latency_client.py | 2 +- src/socketio/admin.py | 2 +- src/socketio/async_client.py | 2 +- src/socketio/async_server.py | 2 +- src/socketio/base_manager.py | 3 +-- src/socketio/base_namespace.py | 2 +- src/socketio/client.py | 2 +- src/socketio/kafka_manager.py | 3 +-- src/socketio/packet.py | 2 +- src/socketio/redis_manager.py | 3 +-- src/socketio/server.py | 2 +- src/socketio/zmq_manager.py | 2 +- tests/async/test_manager.py | 2 +- tests/async/test_server.py | 4 ++-- tests/common/test_client.py | 2 +- tests/common/test_manager.py | 2 +- tests/common/test_server.py | 4 ++-- 20 files changed, 22 insertions(+), 25 deletions(-) diff --git a/examples/client/async/latency_client.py b/examples/client/async/latency_client.py index 00d25392..8f39549b 100644 --- a/examples/client/async/latency_client.py +++ b/examples/client/async/latency_client.py @@ -23,7 +23,7 @@ async def connect(): async def pong_from_server(): global start_timer latency = time.time() - start_timer - print('latency is {0:.2f} ms'.format(latency * 1000)) + print(f'latency is {latency * 1000:.2f} ms') await sio.sleep(1) if sio.connected: await send_ping() diff --git a/examples/client/sync/latency_client.py b/examples/client/sync/latency_client.py index 0328d100..240d214c 100644 --- a/examples/client/sync/latency_client.py +++ b/examples/client/sync/latency_client.py @@ -21,7 +21,7 @@ def connect(): def pong_from_server(): global start_timer latency = time.time() - start_timer - print('latency is {0:.2f} ms'.format(latency * 1000)) + print(f'latency is {latency * 1000:.2f} ms') sio.sleep(1) if sio.connected: send_ping() diff --git a/examples/simple-client/async/latency_client.py b/examples/simple-client/async/latency_client.py index 96387c65..8d69a850 100644 --- a/examples/simple-client/async/latency_client.py +++ b/examples/simple-client/async/latency_client.py @@ -12,7 +12,7 @@ async def main(): while (await sio.receive()) != ['pong_from_server']: pass latency = time.time() - start_timer - print('latency is {0:.2f} ms'.format(latency * 1000)) + print(f'latency is {latency * 1000:.2f} ms') await asyncio.sleep(1) diff --git a/examples/simple-client/sync/latency_client.py b/examples/simple-client/sync/latency_client.py index d5cd853e..c4dea110 100644 --- a/examples/simple-client/sync/latency_client.py +++ b/examples/simple-client/sync/latency_client.py @@ -11,7 +11,7 @@ def main(): while sio.receive() != ['pong_from_server']: pass latency = time.time() - start_timer - print('latency is {0:.2f} ms'.format(latency * 1000)) + print(f'latency is {latency * 1000:.2f} ms') time.sleep(1) diff --git a/src/socketio/admin.py b/src/socketio/admin.py index f317ea26..58c8aff9 100644 --- a/src/socketio/admin.py +++ b/src/socketio/admin.py @@ -16,7 +16,7 @@ def __init__(self): def push(self, type, count=1): timestamp = int(time.time()) * 1000 - key = '{};{}'.format(timestamp, type) + key = f'{timestamp};{type}' if key not in self.buffer: self.buffer[key] = { 'timestamp': timestamp, diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index e79fce0e..7bfd1ac3 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -386,7 +386,7 @@ async def _send_packet(self, pkt): async def _handle_connect(self, namespace, data): namespace = namespace or '/' if namespace not in self.namespaces: - self.logger.info('Namespace {} is connected'.format(namespace)) + self.logger.info(f'Namespace {namespace} is connected') self.namespaces[namespace] = (data or {}).get('sid', self.sid) await self._trigger_event('connect', namespace=namespace) self._connect_event.set() diff --git a/src/socketio/async_server.py b/src/socketio/async_server.py index 1e523ff1..9b0e9774 100644 --- a/src/socketio/async_server.py +++ b/src/socketio/async_server.py @@ -385,7 +385,7 @@ def on_message(sid, msg): async with eio.session(sid) as session: print('received message from ', session['username']) """ - class _session_context_manager(object): + class _session_context_manager: def __init__(self, server, sid, namespace): self.server = server self.sid = sid diff --git a/src/socketio/base_manager.py b/src/socketio/base_manager.py index ca4b0b95..dafa60ac 100644 --- a/src/socketio/base_manager.py +++ b/src/socketio/base_manager.py @@ -37,8 +37,7 @@ def get_participants(self, namespace, room): participants.update(ns[r]._fwdm if r in ns else {}) else: participants = ns[room]._fwdm.copy() if room in ns else {} - for sid, eio_sid in participants.items(): - yield sid, eio_sid + yield from participants.items() def connect(self, eio_sid, namespace): """Register a client connection to a namespace.""" diff --git a/src/socketio/base_namespace.py b/src/socketio/base_namespace.py index 354f75ac..14b5d8fb 100644 --- a/src/socketio/base_namespace.py +++ b/src/socketio/base_namespace.py @@ -1,4 +1,4 @@ -class BaseNamespace(object): +class BaseNamespace: def __init__(self, namespace=None): self.namespace = namespace or '/' diff --git a/src/socketio/client.py b/src/socketio/client.py index d7af4070..b5fa0b4e 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -363,7 +363,7 @@ def _send_packet(self, pkt): def _handle_connect(self, namespace, data): namespace = namespace or '/' if namespace not in self.namespaces: - self.logger.info('Namespace {} is connected'.format(namespace)) + self.logger.info(f'Namespace {namespace} is connected') self.namespaces[namespace] = (data or {}).get('sid', self.sid) self._trigger_event('connect', namespace=namespace) self._connect_event.set() diff --git a/src/socketio/kafka_manager.py b/src/socketio/kafka_manager.py index 4d87d46f..11b87ad8 100644 --- a/src/socketio/kafka_manager.py +++ b/src/socketio/kafka_manager.py @@ -57,8 +57,7 @@ def _publish(self, data): self.producer.flush() def _kafka_listen(self): - for message in self.consumer: - yield message + yield from self.consumer def _listen(self): for message in self._kafka_listen(): diff --git a/src/socketio/packet.py b/src/socketio/packet.py index ec1b364c..f7ad87e6 100644 --- a/src/socketio/packet.py +++ b/src/socketio/packet.py @@ -7,7 +7,7 @@ 'BINARY_EVENT', 'BINARY_ACK'] -class Packet(object): +class Packet: """Socket.IO packet.""" # the format of the Socket.IO packet is as follows: diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index ae9fa292..a16fb2c7 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -94,8 +94,7 @@ def _redis_listen_with_retries(self): self._redis_connect() self.pubsub.subscribe(self.channel) retry_sleep = 1 - for message in self.pubsub.listen(): - yield message + yield from self.pubsub.listen() except redis.exceptions.RedisError: logger.error('Cannot receive from redis... ' 'retrying in {} secs'.format(retry_sleep)) diff --git a/src/socketio/server.py b/src/socketio/server.py index 883b37ab..ae73df6a 100644 --- a/src/socketio/server.py +++ b/src/socketio/server.py @@ -363,7 +363,7 @@ def on_message(sid, msg): with sio.session(sid) as session: print('received message from ', session['username']) """ - class _session_context_manager(object): + class _session_context_manager: def __init__(self, server, sid, namespace): self.server = server self.sid = sid diff --git a/src/socketio/zmq_manager.py b/src/socketio/zmq_manager.py index 760fbc38..468dc268 100644 --- a/src/socketio/zmq_manager.py +++ b/src/socketio/zmq_manager.py @@ -66,7 +66,7 @@ def __init__(self, url='zmq+tcp://localhost:5555+5556', sink.connect(sink_url) sub = zmq.Context().socket(zmq.SUB) - sub.setsockopt_string(zmq.SUBSCRIBE, u'') + sub.setsockopt_string(zmq.SUBSCRIBE, '') sub.connect(sub_url) self.sink = sink diff --git a/tests/async/test_manager.py b/tests/async/test_manager.py index 5d60e039..77b9bdb7 100644 --- a/tests/async/test_manager.py +++ b/tests/async/test_manager.py @@ -392,7 +392,7 @@ def test_emit_binary(self): sid = _run(self.bm.connect('123', '/')) _run( self.bm.emit( - u'my event', b'my binary data', namespace='/', room=sid + 'my event', b'my binary data', namespace='/', room=sid ) ) assert self.bm.server._send_eio_packet.await_count == 2 diff --git a/tests/async/test_server.py b/tests/async/test_server.py index 704982bd..b2de48a9 100644 --- a/tests/async/test_server.py +++ b/tests/async/test_server.py @@ -963,7 +963,7 @@ async def on_baz(self, ns, sid, data1, data2): assert result['result'] == ('disconnect', '1', '/foo') def test_bad_namespace_handler(self, eio): - class Dummy(object): + class Dummy: pass class SyncNS(namespace.Namespace): @@ -1004,7 +1004,7 @@ def test_custom_json(self, eio): # Warning: this test cannot run in parallel with other tests, as it # changes the JSON encoding/decoding functions - class CustomJSON(object): + class CustomJSON: @staticmethod def dumps(*args, **kwargs): return '*** encoded ***' diff --git a/tests/common/test_client.py b/tests/common/test_client.py index c7de4b98..c7399dc1 100644 --- a/tests/common/test_client.py +++ b/tests/common/test_client.py @@ -145,7 +145,7 @@ class MyNamespace(namespace.ClientNamespace): assert c.namespace_handlers['/foo'] == n def test_namespace_handler_wrong_class(self): - class MyNamespace(object): + class MyNamespace: def __init__(self, n): pass diff --git a/tests/common/test_manager.py b/tests/common/test_manager.py index 65ed1cb2..5634c8a9 100644 --- a/tests/common/test_manager.py +++ b/tests/common/test_manager.py @@ -341,7 +341,7 @@ def test_emit_with_none(self): def test_emit_binary(self): sid = self.bm.connect('123', '/') - self.bm.emit(u'my event', b'my binary data', namespace='/', room=sid) + self.bm.emit('my event', b'my binary data', namespace='/', room=sid) assert self.bm.server._send_eio_packet.call_count == 2 assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == '123' pkt = self.bm.server._send_eio_packet.call_args_list[0][0][1] diff --git a/tests/common/test_server.py b/tests/common/test_server.py index e6b02e5a..57ddc2f4 100644 --- a/tests/common/test_server.py +++ b/tests/common/test_server.py @@ -885,7 +885,7 @@ def on_baz(self, ns, sid, data1, data2): assert result['result'] == ('disconnect', '1', '/foo') def test_bad_namespace_handler(self, eio): - class Dummy(object): + class Dummy: pass class AsyncNS(namespace.Namespace): @@ -947,7 +947,7 @@ def test_custom_json(self, eio): # Warning: this test cannot run in parallel with other tests, as it # changes the JSON encoding/decoding functions - class CustomJSON(object): + class CustomJSON: @staticmethod def dumps(*args, **kwargs): return '*** encoded ***' From 78d1124c50cff149051fb89bf6f08000bf184da5 Mon Sep 17 00:00:00 2001 From: Arseny <36441601+arkuzo@users.noreply.github.com> Date: Sat, 14 Dec 2024 03:21:21 +0300 Subject: [PATCH 085/173] fix AsyncClient::wait unexpected return after success reconnect (#1407) * fix AsyncClient::wait unexpected return after success reconnect AsyncClient::wait use sleep(1) call to wait to start reconnect task. Sometimes reconnect is faster then 1 second, and wait returns while connection to server is established. Added one check to avoid this situation * Making added check easier to understand in source code * fix Client::wait unexpected return after success reconnect * fixes --------- Co-authored-by: Miguel Grinberg --- src/socketio/async_client.py | 3 +++ src/socketio/client.py | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index 7bfd1ac3..0d85aa18 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -189,6 +189,9 @@ async def wait(self): await self.eio.wait() await self.sleep(1) # give the reconnect task time to start up if not self._reconnect_task: + if self.eio.state == 'connected': # pragma: no cover + # connected while sleeping above + continue break await self._reconnect_task if self.eio.state != 'connected': diff --git a/src/socketio/client.py b/src/socketio/client.py index b5fa0b4e..00d7961e 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -180,7 +180,12 @@ def wait(self): self.eio.wait() self.sleep(1) # give the reconnect task time to start up if not self._reconnect_task: - break + if self.eio.state == 'connected': # pragma: no cover + # connected while sleeping above + continue + else: + # the reconnect task gave up + break self._reconnect_task.join() if self.eio.state != 'connected': break From b6ee33e56cf2679664c1b894bf7e5d33a30976db Mon Sep 17 00:00:00 2001 From: humayunsr Date: Sat, 14 Dec 2024 15:42:51 +0500 Subject: [PATCH 086/173] Prevent multiple tasks for reconnection (#1369) * Prevent multiple taks for reconnection As discussed here. https://github.com/miguelgrinberg/python-socketio/discussions/1367 In certain scenarios, this library creates multiple reconnection tasks. A check is added to make sure that reconnection task starts only when this task is not running. Signed-off-by: Humayun Ajmal * async client --------- Signed-off-by: Humayun Ajmal Co-authored-by: Miguel Grinberg --- src/socketio/async_client.py | 2 +- src/socketio/client.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index 0d85aa18..fb1abc14 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -581,7 +581,7 @@ async def _handle_eio_disconnect(self): self.callbacks = {} self._binary_packet = None self.sid = None - if will_reconnect: + if will_reconnect and not self._reconnect_task: self._reconnect_task = self.start_background_task( self._handle_reconnect) diff --git a/src/socketio/client.py b/src/socketio/client.py index 00d7961e..c4f9eaa7 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -539,7 +539,7 @@ def _handle_eio_disconnect(self): self.callbacks = {} self._binary_packet = None self.sid = None - if will_reconnect: + if will_reconnect and not self._reconnect_task: self._reconnect_task = self.start_background_task( self._handle_reconnect) From 0b5c4638e5e4bff06fcf46476d218ae5ad4ada14 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 17 Dec 2024 20:24:37 +0000 Subject: [PATCH 087/173] Adopted pyenv-asyncio for async unit tests --- pyproject.toml | 4 + tests/async/helpers.py | 6 - tests/async/test_client.py | 477 +++++++++++------------ tests/async/test_manager.py | 336 ++++++++--------- tests/async/test_namespace.py | 151 ++++---- tests/async/test_pubsub_manager.py | 335 ++++++++--------- tests/async/test_server.py | 582 ++++++++++++++--------------- tests/async/test_simple_client.py | 69 ++-- tox.ini | 1 + 9 files changed, 923 insertions(+), 1038 deletions(-) delete mode 100644 tests/async/helpers.py diff --git a/pyproject.toml b/pyproject.toml index 54ba9d93..8a5453a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,3 +54,7 @@ namespaces = false [build-system] requires = ["setuptools>=61.2"] build-backend = "setuptools.build_meta" + +[tool.pytest.ini_options] +asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "session" diff --git a/tests/async/helpers.py b/tests/async/helpers.py deleted file mode 100644 index c9b708c9..00000000 --- a/tests/async/helpers.py +++ /dev/null @@ -1,6 +0,0 @@ -import asyncio - - -def _run(coro): - """Run the given coroutine.""" - return asyncio.get_event_loop().run_until_complete(coro) diff --git a/tests/async/test_client.py b/tests/async/test_client.py index 289abd94..26681b76 100644 --- a/tests/async/test_client.py +++ b/tests/async/test_client.py @@ -8,27 +8,24 @@ from engineio import exceptions as engineio_exceptions from socketio import exceptions from socketio import packet -from .helpers import _run class TestAsyncClient: - def test_is_asyncio_based(self): + async def test_is_asyncio_based(self): c = async_client.AsyncClient() assert c.is_asyncio_based() - def test_connect(self): + async def test_connect(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() - _run( - c.connect( - 'url', - headers='headers', - auth='auth', - transports='transports', - namespaces=['/foo', '/', '/bar'], - socketio_path='path', - wait=False, - ) + await c.connect( + 'url', + headers='headers', + auth='auth', + transports='transports', + namespaces=['/foo', '/', '/bar'], + socketio_path='path', + wait=False, ) assert c.connection_url == 'url' assert c.connection_headers == 'headers' @@ -43,22 +40,20 @@ def test_connect(self): engineio_path='path', ) - def test_connect_functions(self): + async def test_connect_functions(self): async def headers(): return 'headers' c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() - _run( - c.connect( - lambda: 'url', - headers=headers, - auth='auth', - transports='transports', - namespaces=['/foo', '/', '/bar'], - socketio_path='path', - wait=False, - ) + await c.connect( + lambda: 'url', + headers=headers, + auth='auth', + transports='transports', + namespaces=['/foo', '/', '/bar'], + socketio_path='path', + wait=False, ) c.eio.connect.assert_awaited_once_with( 'url', @@ -67,18 +62,16 @@ async def headers(): engineio_path='path', ) - def test_connect_one_namespace(self): + async def test_connect_one_namespace(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() - _run( - c.connect( - 'url', - headers='headers', - transports='transports', - namespaces='/foo', - socketio_path='path', - wait=False, - ) + await c.connect( + 'url', + headers='headers', + transports='transports', + namespaces='/foo', + socketio_path='path', + wait=False, ) assert c.connection_url == 'url' assert c.connection_headers == 'headers' @@ -92,20 +85,18 @@ def test_connect_one_namespace(self): engineio_path='path', ) - def test_connect_default_namespaces(self): + async def test_connect_default_namespaces(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() c.on('foo', mock.MagicMock(), namespace='/foo') c.on('bar', mock.MagicMock(), namespace='/') c.on('baz', mock.MagicMock(), namespace='*') - _run( - c.connect( - 'url', - headers='headers', - transports='transports', - socketio_path='path', - wait=False, - ) + await c.connect( + 'url', + headers='headers', + transports='transports', + socketio_path='path', + wait=False, ) assert c.connection_url == 'url' assert c.connection_headers == 'headers' @@ -120,17 +111,15 @@ def test_connect_default_namespaces(self): engineio_path='path', ) - def test_connect_no_namespaces(self): + async def test_connect_no_namespaces(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() - _run( - c.connect( - 'url', - headers='headers', - transports='transports', - socketio_path='path', - wait=False, - ) + await c.connect( + 'url', + headers='headers', + transports='transports', + socketio_path='path', + wait=False, ) assert c.connection_url == 'url' assert c.connection_headers == 'headers' @@ -144,7 +133,7 @@ def test_connect_no_namespaces(self): engineio_path='path', ) - def test_connect_error(self): + async def test_connect_error(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock( side_effect=engineio_exceptions.ConnectionError('foo') @@ -152,34 +141,28 @@ def test_connect_error(self): c.on('foo', mock.MagicMock(), namespace='/foo') c.on('bar', mock.MagicMock(), namespace='/') with pytest.raises(exceptions.ConnectionError): - _run( - c.connect( - 'url', - headers='headers', - transports='transports', - socketio_path='path', - wait=False, - ) + await c.connect( + 'url', + headers='headers', + transports='transports', + socketio_path='path', + wait=False, ) - def test_connect_twice(self): + async def test_connect_twice(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() - _run( - c.connect( - 'url', - wait=False, - ) + await c.connect( + 'url', + wait=False, ) with pytest.raises(exceptions.ConnectionError): - _run( - c.connect( - 'url', - wait=False, - ) + await c.connect( + 'url', + wait=False, ) - def test_connect_wait_single_namespace(self): + async def test_connect_wait_single_namespace(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() c._connect_event = mock.MagicMock() @@ -189,16 +172,14 @@ async def mock_connect(): return True c._connect_event.wait = mock_connect - _run( - c.connect( - 'url', - wait=True, - wait_timeout=0.01, - ) + await c.connect( + 'url', + wait=True, + wait_timeout=0.01, ) assert c.connected is True - def test_connect_wait_two_namespaces(self): + async def test_connect_wait_two_namespaces(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() c._connect_event = mock.MagicMock() @@ -213,41 +194,37 @@ async def mock_connect(): return False c._connect_event.wait = mock_connect - _run( - c.connect( - 'url', - namespaces=['/foo', '/bar'], - wait=True, - wait_timeout=0.01, - ) + await c.connect( + 'url', + namespaces=['/foo', '/bar'], + wait=True, + wait_timeout=0.01, ) assert c.connected is True assert c.namespaces == {'/bar': '123', '/foo': '456'} - def test_connect_timeout(self): + async def test_connect_timeout(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() c.disconnect = mock.AsyncMock() with pytest.raises(exceptions.ConnectionError): - _run( - c.connect( - 'url', - wait=True, - wait_timeout=0.01, - ) + await c.connect( + 'url', + wait=True, + wait_timeout=0.01, ) c.disconnect.assert_awaited_once_with() - def test_wait_no_reconnect(self): + async def test_wait_no_reconnect(self): c = async_client.AsyncClient() c.eio.wait = mock.AsyncMock() c.sleep = mock.AsyncMock() c._reconnect_task = None - _run(c.wait()) + await c.wait() c.eio.wait.assert_awaited_once_with() c.sleep.assert_awaited_once_with(1) - def test_wait_reconnect_failed(self): + async def test_wait_reconnect_failed(self): c = async_client.AsyncClient() c.eio.wait = mock.AsyncMock() c.sleep = mock.AsyncMock() @@ -257,11 +234,11 @@ async def fake_wait(): c.eio.state = states.pop(0) c._reconnect_task = fake_wait() - _run(c.wait()) + await c.wait() c.eio.wait.assert_awaited_once_with() c.sleep.assert_awaited_once_with(1) - def test_wait_reconnect_successful(self): + async def test_wait_reconnect_successful(self): c = async_client.AsyncClient() c.eio.wait = mock.AsyncMock() c.sleep = mock.AsyncMock() @@ -272,15 +249,15 @@ async def fake_wait(): c._reconnect_task = fake_wait() c._reconnect_task = fake_wait() - _run(c.wait()) + await c.wait() assert c.eio.wait.await_count == 2 assert c.sleep.await_count == 2 - def test_emit_no_arguments(self): + async def test_emit_no_arguments(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} c._send_packet = mock.AsyncMock() - _run(c.emit('foo')) + await c.emit('foo') expected_packet = packet.Packet( packet.EVENT, namespace='/', data=['foo'], id=None) assert c._send_packet.await_count == 1 @@ -289,11 +266,11 @@ def test_emit_no_arguments(self): == expected_packet.encode() ) - def test_emit_one_argument(self): + async def test_emit_one_argument(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} c._send_packet = mock.AsyncMock() - _run(c.emit('foo', 'bar')) + await c.emit('foo', 'bar') expected_packet = packet.Packet( packet.EVENT, namespace='/', @@ -306,11 +283,11 @@ def test_emit_one_argument(self): == expected_packet.encode() ) - def test_emit_one_argument_list(self): + async def test_emit_one_argument_list(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} c._send_packet = mock.AsyncMock() - _run(c.emit('foo', ['bar', 'baz'])) + await c.emit('foo', ['bar', 'baz']) expected_packet = packet.Packet( packet.EVENT, namespace='/', @@ -323,11 +300,11 @@ def test_emit_one_argument_list(self): == expected_packet.encode() ) - def test_emit_two_arguments(self): + async def test_emit_two_arguments(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} c._send_packet = mock.AsyncMock() - _run(c.emit('foo', ('bar', 'baz'))) + await c.emit('foo', ('bar', 'baz')) expected_packet = packet.Packet( packet.EVENT, namespace='/', @@ -340,11 +317,11 @@ def test_emit_two_arguments(self): == expected_packet.encode() ) - def test_emit_namespace(self): + async def test_emit_namespace(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1'} c._send_packet = mock.AsyncMock() - _run(c.emit('foo', namespace='/foo')) + await c.emit('foo', namespace='/foo') expected_packet = packet.Packet( packet.EVENT, namespace='/foo', data=['foo'], id=None) assert c._send_packet.await_count == 1 @@ -353,18 +330,18 @@ def test_emit_namespace(self): == expected_packet.encode() ) - def test_emit_unknown_namespace(self): + async def test_emit_unknown_namespace(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1'} with pytest.raises(exceptions.BadNamespaceError): - _run(c.emit('foo', namespace='/bar')) + await c.emit('foo', namespace='/bar') - def test_emit_with_callback(self): + async def test_emit_with_callback(self): c = async_client.AsyncClient() c._send_packet = mock.AsyncMock() c._generate_ack_id = mock.MagicMock(return_value=123) c.namespaces = {'/': '1'} - _run(c.emit('foo', callback='cb')) + await c.emit('foo', callback='cb') expected_packet = packet.Packet( packet.EVENT, namespace='/', data=['foo'], id=123) assert c._send_packet.await_count == 1 @@ -374,12 +351,12 @@ def test_emit_with_callback(self): ) c._generate_ack_id.assert_called_once_with('/', 'cb') - def test_emit_namespace_with_callback(self): + async def test_emit_namespace_with_callback(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1'} c._send_packet = mock.AsyncMock() c._generate_ack_id = mock.MagicMock(return_value=123) - _run(c.emit('foo', namespace='/foo', callback='cb')) + await c.emit('foo', namespace='/foo', callback='cb') expected_packet = packet.Packet( packet.EVENT, namespace='/foo', data=['foo'], id=123) assert c._send_packet.await_count == 1 @@ -389,11 +366,11 @@ def test_emit_namespace_with_callback(self): ) c._generate_ack_id.assert_called_once_with('/foo', 'cb') - def test_emit_binary(self): + async def test_emit_binary(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} c._send_packet = mock.AsyncMock() - _run(c.emit('foo', b'bar')) + await c.emit('foo', b'bar') expected_packet = packet.Packet( packet.EVENT, namespace='/', @@ -406,11 +383,11 @@ def test_emit_binary(self): == expected_packet.encode() ) - def test_emit_not_binary(self): + async def test_emit_not_binary(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} c._send_packet = mock.AsyncMock() - _run(c.emit('foo', 'bar')) + await c.emit('foo', 'bar') expected_packet = packet.Packet( packet.EVENT, namespace='/', @@ -423,23 +400,23 @@ def test_emit_not_binary(self): == expected_packet.encode() ) - def test_send(self): + async def test_send(self): c = async_client.AsyncClient() c.emit = mock.AsyncMock() - _run(c.send('data', 'namespace', 'callback')) + await c.send('data', 'namespace', 'callback') c.emit.assert_awaited_once_with( 'message', data='data', namespace='namespace', callback='callback' ) - def test_send_with_defaults(self): + async def test_send_with_defaults(self): c = async_client.AsyncClient() c.emit = mock.AsyncMock() - _run(c.send('data')) + await c.send('data') c.emit.assert_awaited_once_with( 'message', data='data', namespace=None, callback=None ) - def test_call(self): + async def test_call(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} @@ -450,7 +427,7 @@ async def fake_event_wait(): c._generate_ack_id = mock.MagicMock(return_value=123) c.eio = mock.MagicMock() c.eio.create_event.return_value.wait = fake_event_wait - assert _run(c.call('foo')) == ('foo', 321) + assert await c.call('foo') == ('foo', 321) expected_packet = packet.Packet( packet.EVENT, namespace='/', data=['foo'], id=123) assert c._send_packet.await_count == 1 @@ -459,7 +436,7 @@ async def fake_event_wait(): == expected_packet.encode() ) - def test_call_with_timeout(self): + async def test_call_with_timeout(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} @@ -471,7 +448,7 @@ async def fake_event_wait(): c.eio = mock.MagicMock() c.eio.create_event.return_value.wait = fake_event_wait with pytest.raises(exceptions.TimeoutError): - _run(c.call('foo', timeout=0.01)) + await c.call('foo', timeout=0.01) expected_packet = packet.Packet( packet.EVENT, namespace='/', data=['foo'], id=123) assert c._send_packet.await_count == 1 @@ -480,7 +457,7 @@ async def fake_event_wait(): == expected_packet.encode() ) - def test_disconnect(self): + async def test_disconnect(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/': '1'} @@ -489,7 +466,7 @@ def test_disconnect(self): c.eio = mock.MagicMock() c.eio.disconnect = mock.AsyncMock() c.eio.state = 'connected' - _run(c.disconnect()) + await c.disconnect() assert c.connected assert c._trigger_event.await_count == 0 assert c._send_packet.await_count == 1 @@ -500,7 +477,7 @@ def test_disconnect(self): ) c.eio.disconnect.assert_awaited_once_with(abort=True) - def test_disconnect_namespaces(self): + async def test_disconnect_namespaces(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1', '/bar': '2'} c._trigger_event = mock.AsyncMock() @@ -508,7 +485,7 @@ def test_disconnect_namespaces(self): c.eio = mock.MagicMock() c.eio.disconnect = mock.AsyncMock() c.eio.state = 'connected' - _run(c.disconnect()) + await c.disconnect() assert c._trigger_event.await_count == 0 assert c._send_packet.await_count == 2 expected_packet = packet.Packet(packet.DISCONNECT, namespace='/foo') @@ -522,7 +499,7 @@ def test_disconnect_namespaces(self): == expected_packet.encode() ) - def test_start_background_task(self): + async def test_start_background_task(self): c = async_client.AsyncClient() c.eio.start_background_task = mock.MagicMock(return_value='foo') assert c.start_background_task('foo', 'bar', baz='baz') == 'foo' @@ -530,22 +507,22 @@ def test_start_background_task(self): 'foo', 'bar', baz='baz' ) - def test_sleep(self): + async def test_sleep(self): c = async_client.AsyncClient() c.eio.sleep = mock.AsyncMock() - _run(c.sleep(1.23)) + await c.sleep(1.23) c.eio.sleep.assert_awaited_once_with(1.23) - def test_send_packet(self): + async def test_send_packet(self): c = async_client.AsyncClient() c.eio.send = mock.AsyncMock() - _run(c._send_packet(packet.Packet(packet.EVENT, 'foo'))) + await c._send_packet(packet.Packet(packet.EVENT, 'foo')) c.eio.send.assert_awaited_once_with('2"foo"') - def test_send_packet_binary(self): + async def test_send_packet_binary(self): c = async_client.AsyncClient() c.eio.send = mock.AsyncMock() - _run(c._send_packet(packet.Packet(packet.EVENT, b'foo'))) + await c._send_packet(packet.Packet(packet.EVENT, b'foo')) assert c.eio.send.await_args_list == [ mock.call('51-{"_placeholder":true,"num":0}'), mock.call(b'foo'), @@ -554,52 +531,52 @@ def test_send_packet_binary(self): mock.call(b'foo'), ] - def test_send_packet_default_binary(self): + async def test_send_packet_default_binary(self): c = async_client.AsyncClient() c.eio.send = mock.AsyncMock() - _run(c._send_packet(packet.Packet(packet.EVENT, 'foo'))) + await c._send_packet(packet.Packet(packet.EVENT, 'foo')) c.eio.send.assert_awaited_once_with('2"foo"') - def test_handle_connect(self): + async def test_handle_connect(self): c = async_client.AsyncClient() c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() c._send_packet = mock.AsyncMock() - _run(c._handle_connect('/', {'sid': '123'})) + await c._handle_connect('/', {'sid': '123'}) c._connect_event.set.assert_called_once_with() c._trigger_event.assert_awaited_once_with('connect', namespace='/') c._send_packet.assert_not_awaited() - def test_handle_connect_with_namespaces(self): + async def test_handle_connect_with_namespaces(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1', '/bar': '2'} c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() c._send_packet = mock.AsyncMock() - _run(c._handle_connect('/', {'sid': '3'})) + await c._handle_connect('/', {'sid': '3'}) c._connect_event.set.assert_called_once_with() c._trigger_event.assert_awaited_once_with('connect', namespace='/') assert c.namespaces == {'/': '3', '/foo': '1', '/bar': '2'} - def test_handle_connect_namespace(self): + async def test_handle_connect_namespace(self): c = async_client.AsyncClient() c.namespaces = {'/foo': '1'} c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() c._send_packet = mock.AsyncMock() - _run(c._handle_connect('/foo', {'sid': '123'})) - _run(c._handle_connect('/bar', {'sid': '2'})) + await c._handle_connect('/foo', {'sid': '123'}) + await c._handle_connect('/bar', {'sid': '2'}) assert c._trigger_event.await_count == 1 c._connect_event.set.assert_called_once_with() c._trigger_event.assert_awaited_once_with( 'connect', namespace='/bar') assert c.namespaces == {'/foo': '1', '/bar': '2'} - def test_handle_disconnect(self): + async def test_handle_disconnect(self): c = async_client.AsyncClient() c.connected = True c._trigger_event = mock.AsyncMock() - _run(c._handle_disconnect('/')) + await c._handle_disconnect('/') c._trigger_event.assert_any_await( 'disconnect', namespace='/' ) @@ -607,15 +584,15 @@ def test_handle_disconnect(self): '__disconnect_final', namespace='/' ) assert not c.connected - _run(c._handle_disconnect('/')) + await c._handle_disconnect('/') assert c._trigger_event.await_count == 2 - def test_handle_disconnect_namespace(self): + async def test_handle_disconnect_namespace(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} c._trigger_event = mock.AsyncMock() - _run(c._handle_disconnect('/foo')) + await c._handle_disconnect('/foo') c._trigger_event.assert_any_await( 'disconnect', namespace='/foo' ) @@ -624,7 +601,7 @@ def test_handle_disconnect_namespace(self): ) assert c.namespaces == {'/bar': '2'} assert c.connected - _run(c._handle_disconnect('/bar')) + await c._handle_disconnect('/bar') c._trigger_event.assert_any_await( 'disconnect', namespace='/bar' ) @@ -634,12 +611,12 @@ def test_handle_disconnect_namespace(self): assert c.namespaces == {} assert not c.connected - def test_handle_disconnect_unknown_namespace(self): + async def test_handle_disconnect_unknown_namespace(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} c._trigger_event = mock.AsyncMock() - _run(c._handle_disconnect('/baz')) + await c._handle_disconnect('/baz') c._trigger_event.assert_any_await( 'disconnect', namespace='/baz' ) @@ -649,30 +626,30 @@ def test_handle_disconnect_unknown_namespace(self): assert c.namespaces == {'/foo': '1', '/bar': '2'} assert c.connected - def test_handle_disconnect_default_namespaces(self): + async def test_handle_disconnect_default_namespaces(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} c._trigger_event = mock.AsyncMock() - _run(c._handle_disconnect('/')) + await c._handle_disconnect('/') c._trigger_event.assert_any_await('disconnect', namespace='/') c._trigger_event.assert_any_await('__disconnect_final', namespace='/') assert c.namespaces == {'/foo': '1', '/bar': '2'} assert c.connected - def test_handle_event(self): + async def test_handle_event(self): c = async_client.AsyncClient() c._trigger_event = mock.AsyncMock() - _run(c._handle_event('/', None, ['foo', ('bar', 'baz')])) + await c._handle_event('/', None, ['foo', ('bar', 'baz')]) c._trigger_event.assert_awaited_once_with( 'foo', '/', ('bar', 'baz') ) - def test_handle_event_with_id_no_arguments(self): + async def test_handle_event_with_id_no_arguments(self): c = async_client.AsyncClient() c._trigger_event = mock.AsyncMock(return_value=None) c._send_packet = mock.AsyncMock() - _run(c._handle_event('/', 123, ['foo', ('bar', 'baz')])) + await c._handle_event('/', 123, ['foo', ('bar', 'baz')]) c._trigger_event.assert_awaited_once_with( 'foo', '/', ('bar', 'baz') ) @@ -684,11 +661,11 @@ def test_handle_event_with_id_no_arguments(self): == expected_packet.encode() ) - def test_handle_event_with_id_one_argument(self): + async def test_handle_event_with_id_one_argument(self): c = async_client.AsyncClient() c._trigger_event = mock.AsyncMock(return_value='ret') c._send_packet = mock.AsyncMock() - _run(c._handle_event('/', 123, ['foo', ('bar', 'baz')])) + await c._handle_event('/', 123, ['foo', ('bar', 'baz')]) c._trigger_event.assert_awaited_once_with( 'foo', '/', ('bar', 'baz') ) @@ -700,11 +677,11 @@ def test_handle_event_with_id_one_argument(self): == expected_packet.encode() ) - def test_handle_event_with_id_one_list_argument(self): + async def test_handle_event_with_id_one_list_argument(self): c = async_client.AsyncClient() c._trigger_event = mock.AsyncMock(return_value=['a', 'b']) c._send_packet = mock.AsyncMock() - _run(c._handle_event('/', 123, ['foo', ('bar', 'baz')])) + await c._handle_event('/', 123, ['foo', ('bar', 'baz')]) c._trigger_event.assert_awaited_once_with( 'foo', '/', ('bar', 'baz') ) @@ -716,11 +693,11 @@ def test_handle_event_with_id_one_list_argument(self): == expected_packet.encode() ) - def test_handle_event_with_id_two_arguments(self): + async def test_handle_event_with_id_two_arguments(self): c = async_client.AsyncClient() c._trigger_event = mock.AsyncMock(return_value=('a', 'b')) c._send_packet = mock.AsyncMock() - _run(c._handle_event('/', 123, ['foo', ('bar', 'baz')])) + await c._handle_event('/', 123, ['foo', ('bar', 'baz')]) c._trigger_event.assert_awaited_once_with( 'foo', '/', ('bar', 'baz') ) @@ -732,37 +709,37 @@ def test_handle_event_with_id_two_arguments(self): == expected_packet.encode() ) - def test_handle_ack(self): + async def test_handle_ack(self): c = async_client.AsyncClient() mock_cb = mock.MagicMock() c.callbacks['/foo'] = {123: mock_cb} - _run(c._handle_ack('/foo', 123, ['bar', 'baz'])) + await c._handle_ack('/foo', 123, ['bar', 'baz']) mock_cb.assert_called_once_with('bar', 'baz') assert 123 not in c.callbacks['/foo'] - def test_handle_ack_async(self): + async def test_handle_ack_async(self): c = async_client.AsyncClient() mock_cb = mock.AsyncMock() c.callbacks['/foo'] = {123: mock_cb} - _run(c._handle_ack('/foo', 123, ['bar', 'baz'])) + await c._handle_ack('/foo', 123, ['bar', 'baz']) mock_cb.assert_awaited_once_with('bar', 'baz') assert 123 not in c.callbacks['/foo'] - def test_handle_ack_not_found(self): + async def test_handle_ack_not_found(self): c = async_client.AsyncClient() mock_cb = mock.MagicMock() c.callbacks['/foo'] = {123: mock_cb} - _run(c._handle_ack('/foo', 124, ['bar', 'baz'])) + await c._handle_ack('/foo', 124, ['bar', 'baz']) mock_cb.assert_not_called() assert 123 in c.callbacks['/foo'] - def test_handle_error(self): + async def test_handle_error(self): c = async_client.AsyncClient() c.connected = True c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() c.namespaces = {'/foo': '1', '/bar': '2'} - _run(c._handle_error('/', 'error')) + await c._handle_error('/', 'error') assert c.namespaces == {} assert not c.connected c._connect_event.set.assert_called_once_with() @@ -770,25 +747,25 @@ def test_handle_error(self): 'connect_error', '/', 'error' ) - def test_handle_error_with_no_arguments(self): + async def test_handle_error_with_no_arguments(self): c = async_client.AsyncClient() c.connected = True c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() c.namespaces = {'/foo': '1', '/bar': '2'} - _run(c._handle_error('/', None)) + await c._handle_error('/', None) assert c.namespaces == {} assert not c.connected c._connect_event.set.assert_called_once_with() c._trigger_event.assert_awaited_once_with('connect_error', '/') - def test_handle_error_namespace(self): + async def test_handle_error_namespace(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() - _run(c._handle_error('/bar', ['error', 'message'])) + await c._handle_error('/bar', ['error', 'message']) assert c.namespaces == {'/foo': '1'} assert c.connected c._connect_event.set.assert_called_once_with() @@ -796,52 +773,52 @@ def test_handle_error_namespace(self): 'connect_error', '/bar', 'error', 'message' ) - def test_handle_error_namespace_with_no_arguments(self): + async def test_handle_error_namespace_with_no_arguments(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() - _run(c._handle_error('/bar', None)) + await c._handle_error('/bar', None) assert c.namespaces == {'/foo': '1'} assert c.connected c._connect_event.set.assert_called_once_with() c._trigger_event.assert_awaited_once_with('connect_error', '/bar') - def test_handle_error_unknown_namespace(self): + async def test_handle_error_unknown_namespace(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} c._connect_event = mock.MagicMock() - _run(c._handle_error('/baz', 'error')) + await c._handle_error('/baz', 'error') assert c.namespaces == {'/foo': '1', '/bar': '2'} assert c.connected c._connect_event.set.assert_called_once_with() - def test_trigger_event(self): + async def test_trigger_event(self): c = async_client.AsyncClient() handler = mock.MagicMock() catchall_handler = mock.MagicMock() c.on('foo', handler) c.on('*', catchall_handler) - _run(c._trigger_event('foo', '/', 1, '2')) - _run(c._trigger_event('bar', '/', 1, '2', 3)) - _run(c._trigger_event('connect', '/')) # should not trigger + await c._trigger_event('foo', '/', 1, '2') + await c._trigger_event('bar', '/', 1, '2', 3) + await c._trigger_event('connect', '/') # should not trigger handler.assert_called_once_with(1, '2') catchall_handler.assert_called_once_with('bar', 1, '2', 3) - def test_trigger_event_namespace(self): + async def test_trigger_event_namespace(self): c = async_client.AsyncClient() handler = mock.AsyncMock() catchall_handler = mock.AsyncMock() c.on('foo', handler, namespace='/bar') c.on('*', catchall_handler, namespace='/bar') - _run(c._trigger_event('foo', '/bar', 1, '2')) - _run(c._trigger_event('bar', '/bar', 1, '2', 3)) + await c._trigger_event('foo', '/bar', 1, '2') + await c._trigger_event('bar', '/bar', 1, '2', 3) handler.assert_awaited_once_with(1, '2') catchall_handler.assert_awaited_once_with('bar', 1, '2', 3) - def test_trigger_event_class_namespace(self): + async def test_trigger_event_class_namespace(self): c = async_client.AsyncClient() result = [] @@ -851,10 +828,10 @@ def on_foo(self, a, b): result.append(b) c.register_namespace(MyNamespace('/')) - _run(c._trigger_event('foo', '/', 1, '2')) + await c._trigger_event('foo', '/', 1, '2') assert result == [1, '2'] - def test_trigger_event_with_catchall_class_namespace(self): + async def test_trigger_event_with_catchall_class_namespace(self): result = {} class MyNamespace(async_namespace.AsyncClientNamespace): @@ -875,18 +852,18 @@ def on_baz(self, ns, data1, data2): c = async_client.AsyncClient() c.register_namespace(MyNamespace('*')) - _run(c._trigger_event('connect', '/foo')) + await c._trigger_event('connect', '/foo') assert result['result'] == ('/foo',) - _run(c._trigger_event('foo', '/foo', 'a')) + await c._trigger_event('foo', '/foo', 'a') assert result['result'] == ('/foo', 'a') - _run(c._trigger_event('bar', '/foo')) + await c._trigger_event('bar', '/foo') assert result['result'] == 'bar/foo' - _run(c._trigger_event('baz', '/foo', 'a', 'b')) + await c._trigger_event('baz', '/foo', 'a', 'b') assert result['result'] == ('/foo', 'a', 'b') - _run(c._trigger_event('disconnect', '/foo')) + await c._trigger_event('disconnect', '/foo') assert result['result'] == ('disconnect', '/foo') - def test_trigger_event_unknown_namespace(self): + async def test_trigger_event_unknown_namespace(self): c = async_client.AsyncClient() result = [] @@ -896,7 +873,7 @@ def on_foo(self, a, b): result.append(b) c.register_namespace(MyNamespace('/')) - _run(c._trigger_event('foo', '/bar', 1, '2')) + await c._trigger_event('foo', '/bar', 1, '2') assert result == [] @mock.patch( @@ -905,13 +882,13 @@ def on_foo(self, a, b): side_effect=asyncio.TimeoutError, ) @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) - def test_handle_reconnect(self, random, wait_for): + async def test_handle_reconnect(self, random, wait_for): c = async_client.AsyncClient() c._reconnect_task = 'foo' c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] ) - _run(c._handle_reconnect()) + await c._handle_reconnect() assert wait_for.await_count == 3 assert [x[0][1] for x in asyncio.wait_for.await_args_list] == [ 1.5, @@ -926,13 +903,13 @@ def test_handle_reconnect(self, random, wait_for): side_effect=asyncio.TimeoutError, ) @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) - def test_handle_reconnect_max_delay(self, random, wait_for): + async def test_handle_reconnect_max_delay(self, random, wait_for): c = async_client.AsyncClient(reconnection_delay_max=3) c._reconnect_task = 'foo' c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] ) - _run(c._handle_reconnect()) + await c._handle_reconnect() assert wait_for.await_count == 3 assert [x[0][1] for x in asyncio.wait_for.await_args_list] == [ 1.5, @@ -947,7 +924,7 @@ def test_handle_reconnect_max_delay(self, random, wait_for): side_effect=asyncio.TimeoutError, ) @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) - def test_handle_reconnect_max_attempts(self, random, wait_for): + async def test_handle_reconnect_max_attempts(self, random, wait_for): c = async_client.AsyncClient(reconnection_attempts=2, logger=True) c.connection_namespaces = ['/'] c._reconnect_task = 'foo' @@ -955,7 +932,7 @@ def test_handle_reconnect_max_attempts(self, random, wait_for): c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] ) - _run(c._handle_reconnect()) + await c._handle_reconnect() assert wait_for.await_count == 2 assert [x[0][1] for x in asyncio.wait_for.await_args_list] == [ 1.5, @@ -971,7 +948,7 @@ def test_handle_reconnect_max_attempts(self, random, wait_for): side_effect=[asyncio.TimeoutError, None], ) @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) - def test_handle_reconnect_aborted(self, random, wait_for): + async def test_handle_reconnect_aborted(self, random, wait_for): c = async_client.AsyncClient(logger=True) c.connection_namespaces = ['/'] c._reconnect_task = 'foo' @@ -979,7 +956,7 @@ def test_handle_reconnect_aborted(self, random, wait_for): c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] ) - _run(c._handle_reconnect()) + await c._handle_reconnect() assert wait_for.await_count == 2 assert [x[0][1] for x in asyncio.wait_for.await_args_list] == [ 1.5, @@ -989,7 +966,7 @@ def test_handle_reconnect_aborted(self, random, wait_for): c._trigger_event.assert_awaited_once_with('__disconnect_final', namespace='/') - def test_shutdown_disconnect(self): + async def test_shutdown_disconnect(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/': '1'} @@ -998,7 +975,7 @@ def test_shutdown_disconnect(self): c.eio = mock.MagicMock() c.eio.disconnect = mock.AsyncMock() c.eio.state = 'connected' - _run(c.shutdown()) + await c.shutdown() assert c._trigger_event.await_count == 0 assert c._send_packet.await_count == 1 expected_packet = packet.Packet(packet.DISCONNECT, namespace='/') @@ -1008,7 +985,7 @@ def test_shutdown_disconnect(self): ) c.eio.disconnect.assert_awaited_once_with(abort=True) - def test_shutdown_disconnect_namespaces(self): + async def test_shutdown_disconnect_namespaces(self): c = async_client.AsyncClient() c.connected = True c.namespaces = {'/foo': '1', '/bar': '2'} @@ -1017,7 +994,7 @@ def test_shutdown_disconnect_namespaces(self): c.eio = mock.MagicMock() c.eio.disconnect = mock.AsyncMock() c.eio.state = 'connected' - _run(c.shutdown()) + await c.shutdown() assert c._trigger_event.await_count == 0 assert c._send_packet.await_count == 2 expected_packet = packet.Packet(packet.DISCONNECT, namespace='/foo') @@ -1032,7 +1009,7 @@ def test_shutdown_disconnect_namespaces(self): ) @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) - def test_shutdown_reconnect(self, random): + async def test_shutdown_reconnect(self, random): c = async_client.AsyncClient() c.connection_namespaces = ['/'] c._reconnect_task = mock.AsyncMock()() @@ -1045,18 +1022,18 @@ async def r(): await c.shutdown() await task - _run(r()) + await r() c._trigger_event.assert_awaited_once_with('__disconnect_final', namespace='/') - def test_handle_eio_connect(self): + async def test_handle_eio_connect(self): c = async_client.AsyncClient() c.connection_namespaces = ['/', '/foo'] c.connection_auth = 'auth' c._send_packet = mock.AsyncMock() c.eio.sid = 'foo' assert c.sid is None - _run(c._handle_eio_connect()) + await c._handle_eio_connect() assert c.sid == 'foo' assert c._send_packet.await_count == 2 expected_packet = packet.Packet( @@ -1072,14 +1049,14 @@ def test_handle_eio_connect(self): == expected_packet.encode() ) - def test_handle_eio_connect_function(self): + async def test_handle_eio_connect_function(self): c = async_client.AsyncClient() c.connection_namespaces = ['/', '/foo'] c.connection_auth = lambda: 'auth' c._send_packet = mock.AsyncMock() c.eio.sid = 'foo' assert c.sid is None - _run(c._handle_eio_connect()) + await c._handle_eio_connect() assert c.sid == 'foo' assert c._send_packet.await_count == 2 expected_packet = packet.Packet( @@ -1095,7 +1072,7 @@ def test_handle_eio_connect_function(self): == expected_packet.encode() ) - def test_handle_eio_message(self): + async def test_handle_eio_message(self): c = async_client.AsyncClient() c._handle_connect = mock.AsyncMock() c._handle_disconnect = mock.AsyncMock() @@ -1103,48 +1080,46 @@ def test_handle_eio_message(self): c._handle_ack = mock.AsyncMock() c._handle_error = mock.AsyncMock() - _run(c._handle_eio_message('0{"sid":"123"}')) + await c._handle_eio_message('0{"sid":"123"}') c._handle_connect.assert_awaited_with(None, {'sid': '123'}) - _run(c._handle_eio_message('0/foo,{"sid":"123"}')) + await c._handle_eio_message('0/foo,{"sid":"123"}') c._handle_connect.assert_awaited_with('/foo', {'sid': '123'}) - _run(c._handle_eio_message('1')) + await c._handle_eio_message('1') c._handle_disconnect.assert_awaited_with(None) - _run(c._handle_eio_message('1/foo')) + await c._handle_eio_message('1/foo') c._handle_disconnect.assert_awaited_with('/foo') - _run(c._handle_eio_message('2["foo"]')) + await c._handle_eio_message('2["foo"]') c._handle_event.assert_awaited_with(None, None, ['foo']) - _run(c._handle_eio_message('3/foo,["bar"]')) + await c._handle_eio_message('3/foo,["bar"]') c._handle_ack.assert_awaited_with('/foo', None, ['bar']) - _run(c._handle_eio_message('4')) + await c._handle_eio_message('4') c._handle_error.assert_awaited_with(None, None) - _run(c._handle_eio_message('4"foo"')) + await c._handle_eio_message('4"foo"') c._handle_error.assert_awaited_with(None, 'foo') - _run(c._handle_eio_message('4["foo"]')) + await c._handle_eio_message('4["foo"]') c._handle_error.assert_awaited_with(None, ['foo']) - _run(c._handle_eio_message('4/foo')) + await c._handle_eio_message('4/foo') c._handle_error.assert_awaited_with('/foo', None) - _run(c._handle_eio_message('4/foo,["foo","bar"]')) + await c._handle_eio_message('4/foo,["foo","bar"]') c._handle_error.assert_awaited_with('/foo', ['foo', 'bar']) - _run(c._handle_eio_message('51-{"_placeholder":true,"num":0}')) + await c._handle_eio_message('51-{"_placeholder":true,"num":0}') assert c._binary_packet.packet_type == packet.BINARY_EVENT - _run(c._handle_eio_message(b'foo')) + await c._handle_eio_message(b'foo') c._handle_event.assert_awaited_with(None, None, b'foo') - _run( - c._handle_eio_message( - '62-/foo,{"1":{"_placeholder":true,"num":1},' - '"2":{"_placeholder":true,"num":0}}' - ) + await c._handle_eio_message( + '62-/foo,{"1":{"_placeholder":true,"num":1},' + '"2":{"_placeholder":true,"num":0}}' ) assert c._binary_packet.packet_type == packet.BINARY_ACK - _run(c._handle_eio_message(b'bar')) - _run(c._handle_eio_message(b'foo')) + await c._handle_eio_message(b'bar') + await c._handle_eio_message(b'foo') c._handle_ack.assert_awaited_with( '/foo', None, {'1': b'foo', '2': b'bar'} ) with pytest.raises(ValueError): - _run(c._handle_eio_message('9')) + await c._handle_eio_message('9') - def test_eio_disconnect(self): + async def test_eio_disconnect(self): c = async_client.AsyncClient() c.namespaces = {'/': '1'} c.connected = True @@ -1152,41 +1127,41 @@ def test_eio_disconnect(self): c.start_background_task = mock.MagicMock() c.sid = 'foo' c.eio.state = 'connected' - _run(c._handle_eio_disconnect()) + await c._handle_eio_disconnect() c._trigger_event.assert_awaited_once_with( 'disconnect', namespace='/' ) assert c.sid is None assert not c.connected - def test_eio_disconnect_namespaces(self): + async def test_eio_disconnect_namespaces(self): c = async_client.AsyncClient(reconnection=False) c.namespaces = {'/foo': '1', '/bar': '2'} c.connected = True c._trigger_event = mock.AsyncMock() c.sid = 'foo' c.eio.state = 'connected' - _run(c._handle_eio_disconnect()) + await c._handle_eio_disconnect() c._trigger_event.assert_any_await('disconnect', namespace='/foo') c._trigger_event.assert_any_await('disconnect', namespace='/bar') assert c.sid is None assert not c.connected - def test_eio_disconnect_reconnect(self): + async def test_eio_disconnect_reconnect(self): c = async_client.AsyncClient(reconnection=True) c.start_background_task = mock.MagicMock() c.eio.state = 'connected' - _run(c._handle_eio_disconnect()) + await c._handle_eio_disconnect() c.start_background_task.assert_called_once_with(c._handle_reconnect) - def test_eio_disconnect_self_disconnect(self): + async def test_eio_disconnect_self_disconnect(self): c = async_client.AsyncClient(reconnection=True) c.start_background_task = mock.MagicMock() c.eio.state = 'disconnected' - _run(c._handle_eio_disconnect()) + await c._handle_eio_disconnect() c.start_background_task.assert_not_called() - def test_eio_disconnect_no_reconnect(self): + async def test_eio_disconnect_no_reconnect(self): c = async_client.AsyncClient(reconnection=False) c.namespaces = {'/': '1'} c.connected = True @@ -1194,7 +1169,7 @@ def test_eio_disconnect_no_reconnect(self): c.start_background_task = mock.MagicMock() c.sid = 'foo' c.eio.state = 'connected' - _run(c._handle_eio_disconnect()) + await c._handle_eio_disconnect() c._trigger_event.assert_any_await( 'disconnect', namespace='/' ) diff --git a/tests/async/test_manager.py b/tests/async/test_manager.py index 77b9bdb7..fd5fe816 100644 --- a/tests/async/test_manager.py +++ b/tests/async/test_manager.py @@ -2,7 +2,6 @@ from socketio import async_manager from socketio import packet -from .helpers import _run class TestAsyncManager: @@ -23,8 +22,8 @@ def generate_id(): self.bm.set_server(mock_server) self.bm.initialize() - def test_connect(self): - sid = _run(self.bm.connect('123', '/foo')) + async def test_connect(self): + sid = await self.bm.connect('123', '/foo') assert None in self.bm.rooms['/foo'] assert sid in self.bm.rooms['/foo'] assert sid in self.bm.rooms['/foo'][None] @@ -33,9 +32,9 @@ def test_connect(self): assert dict(self.bm.rooms['/foo'][sid]) == {sid: '123'} assert self.bm.sid_from_eio_sid('123', '/foo') == sid - def test_pre_disconnect(self): - sid1 = _run(self.bm.connect('123', '/foo')) - sid2 = _run(self.bm.connect('456', '/foo')) + async def test_pre_disconnect(self): + sid1 = await self.bm.connect('123', '/foo') + sid2 = await self.bm.connect('456', '/foo') assert self.bm.is_connected(sid1, '/foo') assert self.bm.pre_disconnect(sid1, '/foo') == '123' assert self.bm.pending_disconnect == {'/foo': [sid1]} @@ -43,124 +42,124 @@ def test_pre_disconnect(self): assert self.bm.pre_disconnect(sid2, '/foo') == '456' assert self.bm.pending_disconnect == {'/foo': [sid1, sid2]} assert not self.bm.is_connected(sid2, '/foo') - _run(self.bm.disconnect(sid1, '/foo')) + await self.bm.disconnect(sid1, '/foo') assert self.bm.pending_disconnect == {'/foo': [sid2]} - _run(self.bm.disconnect(sid2, '/foo')) + await self.bm.disconnect(sid2, '/foo') assert self.bm.pending_disconnect == {} - def test_disconnect(self): - sid1 = _run(self.bm.connect('123', '/foo')) - sid2 = _run(self.bm.connect('456', '/foo')) - _run(self.bm.enter_room(sid1, '/foo', 'bar')) - _run(self.bm.enter_room(sid2, '/foo', 'baz')) - _run(self.bm.disconnect(sid1, '/foo')) + async def test_disconnect(self): + sid1 = await self.bm.connect('123', '/foo') + sid2 = await self.bm.connect('456', '/foo') + await self.bm.enter_room(sid1, '/foo', 'bar') + await self.bm.enter_room(sid2, '/foo', 'baz') + await self.bm.disconnect(sid1, '/foo') assert dict(self.bm.rooms['/foo'][None]) == {sid2: '456'} assert dict(self.bm.rooms['/foo'][sid2]) == {sid2: '456'} assert dict(self.bm.rooms['/foo']['baz']) == {sid2: '456'} - def test_disconnect_default_namespace(self): - sid1 = _run(self.bm.connect('123', '/')) - sid2 = _run(self.bm.connect('123', '/foo')) - sid3 = _run(self.bm.connect('456', '/')) - sid4 = _run(self.bm.connect('456', '/foo')) + async def test_disconnect_default_namespace(self): + sid1 = await self.bm.connect('123', '/') + sid2 = await self.bm.connect('123', '/foo') + sid3 = await self.bm.connect('456', '/') + sid4 = await self.bm.connect('456', '/foo') assert self.bm.is_connected(sid1, '/') assert self.bm.is_connected(sid2, '/foo') assert not self.bm.is_connected(sid2, '/') assert not self.bm.is_connected(sid1, '/foo') - _run(self.bm.disconnect(sid1, '/')) + await self.bm.disconnect(sid1, '/') assert not self.bm.is_connected(sid1, '/') assert self.bm.is_connected(sid2, '/foo') - _run(self.bm.disconnect(sid2, '/foo')) + await self.bm.disconnect(sid2, '/foo') assert not self.bm.is_connected(sid2, '/foo') assert dict(self.bm.rooms['/'][None]) == {sid3: '456'} assert dict(self.bm.rooms['/'][sid3]) == {sid3: '456'} assert dict(self.bm.rooms['/foo'][None]) == {sid4: '456'} assert dict(self.bm.rooms['/foo'][sid4]) == {sid4: '456'} - def test_disconnect_twice(self): - sid1 = _run(self.bm.connect('123', '/')) - sid2 = _run(self.bm.connect('123', '/foo')) - sid3 = _run(self.bm.connect('456', '/')) - sid4 = _run(self.bm.connect('456', '/foo')) - _run(self.bm.disconnect(sid1, '/')) - _run(self.bm.disconnect(sid2, '/foo')) - _run(self.bm.disconnect(sid1, '/')) - _run(self.bm.disconnect(sid2, '/foo')) + async def test_disconnect_twice(self): + sid1 = await self.bm.connect('123', '/') + sid2 = await self.bm.connect('123', '/foo') + sid3 = await self.bm.connect('456', '/') + sid4 = await self.bm.connect('456', '/foo') + await self.bm.disconnect(sid1, '/') + await self.bm.disconnect(sid2, '/foo') + await self.bm.disconnect(sid1, '/') + await self.bm.disconnect(sid2, '/foo') assert dict(self.bm.rooms['/'][None]) == {sid3: '456'} assert dict(self.bm.rooms['/'][sid3]) == {sid3: '456'} assert dict(self.bm.rooms['/foo'][None]) == {sid4: '456'} assert dict(self.bm.rooms['/foo'][sid4]) == {sid4: '456'} - def test_disconnect_all(self): - sid1 = _run(self.bm.connect('123', '/foo')) - sid2 = _run(self.bm.connect('456', '/foo')) - _run(self.bm.enter_room(sid1, '/foo', 'bar')) - _run(self.bm.enter_room(sid2, '/foo', 'baz')) - _run(self.bm.disconnect(sid1, '/foo')) - _run(self.bm.disconnect(sid2, '/foo')) + async def test_disconnect_all(self): + sid1 = await self.bm.connect('123', '/foo') + sid2 = await self.bm.connect('456', '/foo') + await self.bm.enter_room(sid1, '/foo', 'bar') + await self.bm.enter_room(sid2, '/foo', 'baz') + await self.bm.disconnect(sid1, '/foo') + await self.bm.disconnect(sid2, '/foo') assert self.bm.rooms == {} - def test_disconnect_with_callbacks(self): - sid1 = _run(self.bm.connect('123', '/')) - sid2 = _run(self.bm.connect('123', '/foo')) - sid3 = _run(self.bm.connect('456', '/foo')) + async def test_disconnect_with_callbacks(self): + sid1 = await self.bm.connect('123', '/') + sid2 = await self.bm.connect('123', '/foo') + sid3 = await self.bm.connect('456', '/foo') self.bm._generate_ack_id(sid1, 'f') self.bm._generate_ack_id(sid2, 'g') self.bm._generate_ack_id(sid3, 'h') - _run(self.bm.disconnect(sid2, '/foo')) + await self.bm.disconnect(sid2, '/foo') assert sid2 not in self.bm.callbacks - _run(self.bm.disconnect(sid1, '/')) + await self.bm.disconnect(sid1, '/') assert sid1 not in self.bm.callbacks assert sid3 in self.bm.callbacks - def test_trigger_sync_callback(self): - sid1 = _run(self.bm.connect('123', '/')) - sid2 = _run(self.bm.connect('123', '/foo')) + async def test_trigger_sync_callback(self): + sid1 = await self.bm.connect('123', '/') + sid2 = await self.bm.connect('123', '/foo') cb = mock.MagicMock() id1 = self.bm._generate_ack_id(sid1, cb) id2 = self.bm._generate_ack_id(sid2, cb) - _run(self.bm.trigger_callback(sid1, id1, ['foo'])) - _run(self.bm.trigger_callback(sid2, id2, ['bar', 'baz'])) + await self.bm.trigger_callback(sid1, id1, ['foo']) + await self.bm.trigger_callback(sid2, id2, ['bar', 'baz']) assert cb.call_count == 2 cb.assert_any_call('foo') cb.assert_any_call('bar', 'baz') - def test_trigger_async_callback(self): - sid1 = _run(self.bm.connect('123', '/')) - sid2 = _run(self.bm.connect('123', '/foo')) + async def test_trigger_async_callback(self): + sid1 = await self.bm.connect('123', '/') + sid2 = await self.bm.connect('123', '/foo') cb = mock.AsyncMock() id1 = self.bm._generate_ack_id(sid1, cb) id2 = self.bm._generate_ack_id(sid2, cb) - _run(self.bm.trigger_callback(sid1, id1, ['foo'])) - _run(self.bm.trigger_callback(sid2, id2, ['bar', 'baz'])) + await self.bm.trigger_callback(sid1, id1, ['foo']) + await self.bm.trigger_callback(sid2, id2, ['bar', 'baz']) assert cb.await_count == 2 cb.assert_any_await('foo') cb.assert_any_await('bar', 'baz') - def test_invalid_callback(self): - sid = _run(self.bm.connect('123', '/')) + async def test_invalid_callback(self): + sid = await self.bm.connect('123', '/') cb = mock.MagicMock() id = self.bm._generate_ack_id(sid, cb) # these should not raise an exception - _run(self.bm.trigger_callback('xxx', id, ['foo'])) - _run(self.bm.trigger_callback(sid, id + 1, ['foo'])) + await self.bm.trigger_callback('xxx', id, ['foo']) + await self.bm.trigger_callback(sid, id + 1, ['foo']) assert cb.call_count == 0 - def test_get_namespaces(self): + async def test_get_namespaces(self): assert list(self.bm.get_namespaces()) == [] - _run(self.bm.connect('123', '/')) - _run(self.bm.connect('123', '/foo')) + await self.bm.connect('123', '/') + await self.bm.connect('123', '/foo') namespaces = list(self.bm.get_namespaces()) assert len(namespaces) == 2 assert '/' in namespaces assert '/foo' in namespaces - def test_get_participants(self): - sid1 = _run(self.bm.connect('123', '/')) - sid2 = _run(self.bm.connect('456', '/')) - sid3 = _run(self.bm.connect('789', '/')) - _run(self.bm.disconnect(sid3, '/')) + async def test_get_participants(self): + sid1 = await self.bm.connect('123', '/') + sid2 = await self.bm.connect('456', '/') + sid3 = await self.bm.connect('789', '/') + await self.bm.disconnect(sid3, '/') assert sid3 not in self.bm.rooms['/'][None] participants = list(self.bm.get_participants('/', None)) assert len(participants) == 2 @@ -168,44 +167,42 @@ def test_get_participants(self): assert (sid2, '456') in participants assert (sid3, '789') not in participants - def test_leave_invalid_room(self): - sid = _run(self.bm.connect('123', '/foo')) - _run(self.bm.leave_room(sid, '/foo', 'baz')) - _run(self.bm.leave_room(sid, '/bar', 'baz')) + async def test_leave_invalid_room(self): + sid = await self.bm.connect('123', '/foo') + await self.bm.leave_room(sid, '/foo', 'baz') + await self.bm.leave_room(sid, '/bar', 'baz') - def test_no_room(self): + async def test_no_room(self): rooms = self.bm.get_rooms('123', '/foo') assert [] == rooms - def test_close_room(self): - sid = _run(self.bm.connect('123', '/foo')) - _run(self.bm.connect('456', '/foo')) - _run(self.bm.connect('789', '/foo')) - _run(self.bm.enter_room(sid, '/foo', 'bar')) - _run(self.bm.enter_room(sid, '/foo', 'bar')) - _run(self.bm.close_room('bar', '/foo')) + async def test_close_room(self): + sid = await self.bm.connect('123', '/foo') + await self.bm.connect('456', '/foo') + await self.bm.connect('789', '/foo') + await self.bm.enter_room(sid, '/foo', 'bar') + await self.bm.enter_room(sid, '/foo', 'bar') + await self.bm.close_room('bar', '/foo') from pprint import pprint pprint(self.bm.rooms) assert 'bar' not in self.bm.rooms['/foo'] - def test_close_invalid_room(self): + async def test_close_invalid_room(self): self.bm.close_room('bar', '/foo') - def test_rooms(self): - sid = _run(self.bm.connect('123', '/foo')) - _run(self.bm.enter_room(sid, '/foo', 'bar')) + async def test_rooms(self): + sid = await self.bm.connect('123', '/foo') + await self.bm.enter_room(sid, '/foo', 'bar') r = self.bm.get_rooms(sid, '/foo') assert len(r) == 2 assert sid in r assert 'bar' in r - def test_emit_to_sid(self): - sid = _run(self.bm.connect('123', '/foo')) - _run(self.bm.connect('456', '/foo')) - _run( - self.bm.emit( - 'my event', {'foo': 'bar'}, namespace='/foo', to=sid - ) + async def test_emit_to_sid(self): + sid = await self.bm.connect('123', '/foo') + await self.bm.connect('456', '/foo') + await self.bm.emit( + 'my event', {'foo': 'bar'}, namespace='/foo', to=sid ) assert self.bm.server._send_eio_packet.await_count == 1 assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ @@ -213,16 +210,14 @@ def test_emit_to_sid(self): pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' - def test_emit_to_room(self): - sid1 = _run(self.bm.connect('123', '/foo')) - _run(self.bm.enter_room(sid1, '/foo', 'bar')) - sid2 = _run(self.bm.connect('456', '/foo')) - _run(self.bm.enter_room(sid2, '/foo', 'bar')) - _run(self.bm.connect('789', '/foo')) - _run( - self.bm.emit( - 'my event', {'foo': 'bar'}, namespace='/foo', room='bar' - ) + async def test_emit_to_room(self): + sid1 = await self.bm.connect('123', '/foo') + await self.bm.enter_room(sid1, '/foo', 'bar') + sid2 = await self.bm.connect('456', '/foo') + await self.bm.enter_room(sid2, '/foo', 'bar') + await self.bm.connect('789', '/foo') + await self.bm.emit( + 'my event', {'foo': 'bar'}, namespace='/foo', room='bar' ) assert self.bm.server._send_eio_packet.await_count == 2 assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ @@ -234,18 +229,16 @@ def test_emit_to_room(self): == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' - def test_emit_to_rooms(self): - sid1 = _run(self.bm.connect('123', '/foo')) - _run(self.bm.enter_room(sid1, '/foo', 'bar')) - sid2 = _run(self.bm.connect('456', '/foo')) - _run(self.bm.enter_room(sid2, '/foo', 'bar')) - _run(self.bm.enter_room(sid2, '/foo', 'baz')) - sid3 = _run(self.bm.connect('789', '/foo')) - _run(self.bm.enter_room(sid3, '/foo', 'baz')) - _run( - self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo', - room=['bar', 'baz']) - ) + async def test_emit_to_rooms(self): + sid1 = await self.bm.connect('123', '/foo') + await self.bm.enter_room(sid1, '/foo', 'bar') + sid2 = await self.bm.connect('456', '/foo') + await self.bm.enter_room(sid2, '/foo', 'bar') + await self.bm.enter_room(sid2, '/foo', 'baz') + sid3 = await self.bm.connect('789', '/foo') + await self.bm.enter_room(sid3, '/foo', 'baz') + await self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo', + room=['bar', 'baz']) assert self.bm.server._send_eio_packet.await_count == 3 assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' @@ -260,14 +253,14 @@ def test_emit_to_rooms(self): == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' - def test_emit_to_all(self): - sid1 = _run(self.bm.connect('123', '/foo')) - _run(self.bm.enter_room(sid1, '/foo', 'bar')) - sid2 = _run(self.bm.connect('456', '/foo')) - _run(self.bm.enter_room(sid2, '/foo', 'bar')) - _run(self.bm.connect('789', '/foo')) - _run(self.bm.connect('abc', '/bar')) - _run(self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo')) + async def test_emit_to_all(self): + sid1 = await self.bm.connect('123', '/foo') + await self.bm.enter_room(sid1, '/foo', 'bar') + sid2 = await self.bm.connect('456', '/foo') + await self.bm.enter_room(sid2, '/foo', 'bar') + await self.bm.connect('789', '/foo') + await self.bm.connect('abc', '/bar') + await self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo') assert self.bm.server._send_eio_packet.await_count == 3 assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ == '123' @@ -282,17 +275,15 @@ def test_emit_to_all(self): == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' - def test_emit_to_all_skip_one(self): - sid1 = _run(self.bm.connect('123', '/foo')) - _run(self.bm.enter_room(sid1, '/foo', 'bar')) - sid2 = _run(self.bm.connect('456', '/foo')) - _run(self.bm.enter_room(sid2, '/foo', 'bar')) - _run(self.bm.connect('789', '/foo')) - _run(self.bm.connect('abc', '/bar')) - _run( - self.bm.emit( - 'my event', {'foo': 'bar'}, namespace='/foo', skip_sid=sid2 - ) + async def test_emit_to_all_skip_one(self): + sid1 = await self.bm.connect('123', '/foo') + await self.bm.enter_room(sid1, '/foo', 'bar') + sid2 = await self.bm.connect('456', '/foo') + await self.bm.enter_room(sid2, '/foo', 'bar') + await self.bm.connect('789', '/foo') + await self.bm.connect('abc', '/bar') + await self.bm.emit( + 'my event', {'foo': 'bar'}, namespace='/foo', skip_sid=sid2 ) assert self.bm.server._send_eio_packet.await_count == 2 assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ @@ -304,20 +295,18 @@ def test_emit_to_all_skip_one(self): == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' - def test_emit_to_all_skip_two(self): - sid1 = _run(self.bm.connect('123', '/foo')) - _run(self.bm.enter_room(sid1, '/foo', 'bar')) - sid2 = _run(self.bm.connect('456', '/foo')) - _run(self.bm.enter_room(sid2, '/foo', 'bar')) - sid3 = _run(self.bm.connect('789', '/foo')) - _run(self.bm.connect('abc', '/bar')) - _run( - self.bm.emit( - 'my event', - {'foo': 'bar'}, - namespace='/foo', - skip_sid=[sid1, sid3], - ) + async def test_emit_to_all_skip_two(self): + sid1 = await self.bm.connect('123', '/foo') + await self.bm.enter_room(sid1, '/foo', 'bar') + sid2 = await self.bm.connect('456', '/foo') + await self.bm.enter_room(sid2, '/foo', 'bar') + sid3 = await self.bm.connect('789', '/foo') + await self.bm.connect('abc', '/bar') + await self.bm.emit( + 'my event', + {'foo': 'bar'}, + namespace='/foo', + skip_sid=[sid1, sid3], ) assert self.bm.server._send_eio_packet.await_count == 1 assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ @@ -325,14 +314,12 @@ def test_emit_to_all_skip_two(self): pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' - def test_emit_with_callback(self): - sid = _run(self.bm.connect('123', '/foo')) + async def test_emit_with_callback(self): + sid = await self.bm.connect('123', '/foo') self.bm._generate_ack_id = mock.MagicMock() self.bm._generate_ack_id.return_value = 11 - _run( - self.bm.emit( - 'my event', {'foo': 'bar'}, namespace='/foo', callback='cb' - ) + await self.bm.emit( + 'my event', {'foo': 'bar'}, namespace='/foo', callback='cb' ) self.bm._generate_ack_id.assert_called_once_with(sid, 'cb') assert self.bm.server._send_packet.await_count == 1 @@ -341,20 +328,17 @@ def test_emit_with_callback(self): pkt = self.bm.server._send_packet.await_args_list[0][0][1] assert pkt.encode() == '2/foo,11["my event",{"foo":"bar"}]' - def test_emit_to_invalid_room(self): - _run( - self.bm.emit('my event', {'foo': 'bar'}, namespace='/', room='123') - ) + async def test_emit_to_invalid_room(self): + await self.bm.emit('my event', {'foo': 'bar'}, namespace='/', + room='123') - def test_emit_to_invalid_namespace(self): - _run(self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo')) + async def test_emit_to_invalid_namespace(self): + await self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo') - def test_emit_with_tuple(self): - sid = _run(self.bm.connect('123', '/foo')) - _run( - self.bm.emit( - 'my event', ('foo', 'bar'), namespace='/foo', room=sid - ) + async def test_emit_with_tuple(self): + sid = await self.bm.connect('123', '/foo') + await self.bm.emit( + 'my event', ('foo', 'bar'), namespace='/foo', room=sid ) assert self.bm.server._send_eio_packet.await_count == 1 assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ @@ -362,12 +346,10 @@ def test_emit_with_tuple(self): pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event","foo","bar"]' - def test_emit_with_list(self): - sid = _run(self.bm.connect('123', '/foo')) - _run( - self.bm.emit( - 'my event', ['foo', 'bar'], namespace='/foo', room=sid - ) + async def test_emit_with_list(self): + sid = await self.bm.connect('123', '/foo') + await self.bm.emit( + 'my event', ['foo', 'bar'], namespace='/foo', room=sid ) assert self.bm.server._send_eio_packet.await_count == 1 assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ @@ -375,12 +357,10 @@ def test_emit_with_list(self): pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event",["foo","bar"]]' - def test_emit_with_none(self): - sid = _run(self.bm.connect('123', '/foo')) - _run( - self.bm.emit( - 'my event', None, namespace='/foo', room=sid - ) + async def test_emit_with_none(self): + sid = await self.bm.connect('123', '/foo') + await self.bm.emit( + 'my event', None, namespace='/foo', room=sid ) assert self.bm.server._send_eio_packet.await_count == 1 assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ @@ -388,12 +368,10 @@ def test_emit_with_none(self): pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event"]' - def test_emit_binary(self): - sid = _run(self.bm.connect('123', '/')) - _run( - self.bm.emit( - 'my event', b'my binary data', namespace='/', room=sid - ) + async def test_emit_binary(self): + sid = await self.bm.connect('123', '/') + await self.bm.emit( + 'my event', b'my binary data', namespace='/', room=sid ) assert self.bm.server._send_eio_packet.await_count == 2 assert self.bm.server._send_eio_packet.await_args_list[0][0][0] \ diff --git a/tests/async/test_namespace.py b/tests/async/test_namespace.py index 62560159..ad9b1a03 100644 --- a/tests/async/test_namespace.py +++ b/tests/async/test_namespace.py @@ -1,11 +1,10 @@ from unittest import mock from socketio import async_namespace -from .helpers import _run class TestAsyncNamespace: - def test_connect_event(self): + async def test_connect_event(self): result = {} class MyNamespace(async_namespace.AsyncNamespace): @@ -14,10 +13,10 @@ async def on_connect(self, sid, environ): ns = MyNamespace('/foo') ns._set_server(mock.MagicMock()) - _run(ns.trigger_event('connect', 'sid', {'foo': 'bar'})) + await ns.trigger_event('connect', 'sid', {'foo': 'bar'}) assert result['result'] == ('sid', {'foo': 'bar'}) - def test_disconnect_event(self): + async def test_disconnect_event(self): result = {} class MyNamespace(async_namespace.AsyncNamespace): @@ -26,10 +25,10 @@ async def on_disconnect(self, sid): ns = MyNamespace('/foo') ns._set_server(mock.MagicMock()) - _run(ns.trigger_event('disconnect', 'sid')) + await ns.trigger_event('disconnect', 'sid') assert result['result'] == 'sid' - def test_sync_event(self): + async def test_sync_event(self): result = {} class MyNamespace(async_namespace.AsyncNamespace): @@ -38,10 +37,10 @@ def on_custom_message(self, sid, data): ns = MyNamespace('/foo') ns._set_server(mock.MagicMock()) - _run(ns.trigger_event('custom_message', 'sid', {'data': 'data'})) + await ns.trigger_event('custom_message', 'sid', {'data': 'data'}) assert result['result'] == ('sid', {'data': 'data'}) - def test_async_event(self): + async def test_async_event(self): result = {} class MyNamespace(async_namespace.AsyncNamespace): @@ -50,10 +49,10 @@ async def on_custom_message(self, sid, data): ns = MyNamespace('/foo') ns._set_server(mock.MagicMock()) - _run(ns.trigger_event('custom_message', 'sid', {'data': 'data'})) + await ns.trigger_event('custom_message', 'sid', {'data': 'data'}) assert result['result'] == ('sid', {'data': 'data'}) - def test_event_not_found(self): + async def test_event_not_found(self): result = {} class MyNamespace(async_namespace.AsyncNamespace): @@ -62,20 +61,17 @@ async def on_custom_message(self, sid, data): ns = MyNamespace('/foo') ns._set_server(mock.MagicMock()) - _run( - ns.trigger_event('another_custom_message', 'sid', {'data': 'data'}) - ) + await ns.trigger_event('another_custom_message', 'sid', + {'data': 'data'}) assert result == {} - def test_emit(self): + async def test_emit(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() mock_server.emit = mock.AsyncMock() ns._set_server(mock_server) - _run( - ns.emit( - 'ev', data='data', to='room', skip_sid='skip', callback='cb' - ) + await ns.emit( + 'ev', data='data', to='room', skip_sid='skip', callback='cb' ) ns.server.emit.assert_awaited_with( 'ev', @@ -87,16 +83,14 @@ def test_emit(self): callback='cb', ignore_queue=False, ) - _run( - ns.emit( - 'ev', - data='data', - room='room', - skip_sid='skip', - namespace='/bar', - callback='cb', - ignore_queue=True, - ) + await ns.emit( + 'ev', + data='data', + room='room', + skip_sid='skip', + namespace='/bar', + callback='cb', + ignore_queue=True, ) ns.server.emit.assert_awaited_with( 'ev', @@ -109,12 +103,12 @@ def test_emit(self): ignore_queue=True, ) - def test_send(self): + async def test_send(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() mock_server.send = mock.AsyncMock() ns._set_server(mock_server) - _run(ns.send(data='data', to='room', skip_sid='skip', callback='cb')) + await ns.send(data='data', to='room', skip_sid='skip', callback='cb') ns.server.send.assert_awaited_with( 'data', to='room', @@ -124,15 +118,13 @@ def test_send(self): callback='cb', ignore_queue=False, ) - _run( - ns.send( - data='data', - room='room', - skip_sid='skip', - namespace='/bar', - callback='cb', - ignore_queue=True, - ) + await ns.send( + data='data', + room='room', + skip_sid='skip', + namespace='/bar', + callback='cb', + ignore_queue=True, ) ns.server.send.assert_awaited_with( 'data', @@ -144,12 +136,12 @@ def test_send(self): ignore_queue=True, ) - def test_call(self): + async def test_call(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() mock_server.call = mock.AsyncMock() ns._set_server(mock_server) - _run(ns.call('ev', data='data', to='sid')) + await ns.call('ev', data='data', to='sid') ns.server.call.assert_awaited_with( 'ev', data='data', @@ -159,8 +151,8 @@ def test_call(self): timeout=None, ignore_queue=False, ) - _run(ns.call('ev', data='data', sid='sid', namespace='/bar', - timeout=45, ignore_queue=True)) + await ns.call('ev', data='data', sid='sid', namespace='/bar', + timeout=45, ignore_queue=True) ns.server.call.assert_awaited_with( 'ev', data='data', @@ -171,45 +163,45 @@ def test_call(self): ignore_queue=True, ) - def test_enter_room(self): + async def test_enter_room(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() mock_server.enter_room = mock.AsyncMock() ns._set_server(mock_server) - _run(ns.enter_room('sid', 'room')) + await ns.enter_room('sid', 'room') ns.server.enter_room.assert_awaited_with( 'sid', 'room', namespace='/foo' ) - _run(ns.enter_room('sid', 'room', namespace='/bar')) + await ns.enter_room('sid', 'room', namespace='/bar') ns.server.enter_room.assert_awaited_with( 'sid', 'room', namespace='/bar' ) - def test_leave_room(self): + async def test_leave_room(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() mock_server.leave_room = mock.AsyncMock() ns._set_server(mock_server) - _run(ns.leave_room('sid', 'room')) + await ns.leave_room('sid', 'room') ns.server.leave_room.assert_awaited_with( 'sid', 'room', namespace='/foo' ) - _run(ns.leave_room('sid', 'room', namespace='/bar')) + await ns.leave_room('sid', 'room', namespace='/bar') ns.server.leave_room.assert_awaited_with( 'sid', 'room', namespace='/bar' ) - def test_close_room(self): + async def test_close_room(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() mock_server.close_room = mock.AsyncMock() ns._set_server(mock_server) - _run(ns.close_room('room')) + await ns.close_room('room') ns.server.close_room.assert_awaited_with('room', namespace='/foo') - _run(ns.close_room('room', namespace='/bar')) + await ns.close_room('room', namespace='/bar') ns.server.close_room.assert_awaited_with('room', namespace='/bar') - def test_rooms(self): + async def test_rooms(self): ns = async_namespace.AsyncNamespace('/foo') ns._set_server(mock.MagicMock()) ns.rooms('sid') @@ -217,21 +209,21 @@ def test_rooms(self): ns.rooms('sid', namespace='/bar') ns.server.rooms.assert_called_with('sid', namespace='/bar') - def test_session(self): + async def test_session(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() mock_server.get_session = mock.AsyncMock() mock_server.save_session = mock.AsyncMock() ns._set_server(mock_server) - _run(ns.get_session('sid')) + await ns.get_session('sid') ns.server.get_session.assert_awaited_with('sid', namespace='/foo') - _run(ns.get_session('sid', namespace='/bar')) + await ns.get_session('sid', namespace='/bar') ns.server.get_session.assert_awaited_with('sid', namespace='/bar') - _run(ns.save_session('sid', {'a': 'b'})) + await ns.save_session('sid', {'a': 'b'}) ns.server.save_session.assert_awaited_with( 'sid', {'a': 'b'}, namespace='/foo' ) - _run(ns.save_session('sid', {'a': 'b'}, namespace='/bar')) + await ns.save_session('sid', {'a': 'b'}, namespace='/bar') ns.server.save_session.assert_awaited_with( 'sid', {'a': 'b'}, namespace='/bar' ) @@ -240,17 +232,17 @@ def test_session(self): ns.session('sid', namespace='/bar') ns.server.session.assert_called_with('sid', namespace='/bar') - def test_disconnect(self): + async def test_disconnect(self): ns = async_namespace.AsyncNamespace('/foo') mock_server = mock.MagicMock() mock_server.disconnect = mock.AsyncMock() ns._set_server(mock_server) - _run(ns.disconnect('sid')) + await ns.disconnect('sid') ns.server.disconnect.assert_awaited_with('sid', namespace='/foo') - _run(ns.disconnect('sid', namespace='/bar')) + await ns.disconnect('sid', namespace='/bar') ns.server.disconnect.assert_awaited_with('sid', namespace='/bar') - def test_sync_event_client(self): + async def test_sync_event_client(self): result = {} class MyNamespace(async_namespace.AsyncClientNamespace): @@ -259,10 +251,10 @@ def on_custom_message(self, sid, data): ns = MyNamespace('/foo') ns._set_client(mock.MagicMock()) - _run(ns.trigger_event('custom_message', 'sid', {'data': 'data'})) + await ns.trigger_event('custom_message', 'sid', {'data': 'data'}) assert result['result'] == ('sid', {'data': 'data'}) - def test_async_event_client(self): + async def test_async_event_client(self): result = {} class MyNamespace(async_namespace.AsyncClientNamespace): @@ -271,10 +263,10 @@ async def on_custom_message(self, sid, data): ns = MyNamespace('/foo') ns._set_client(mock.MagicMock()) - _run(ns.trigger_event('custom_message', 'sid', {'data': 'data'})) + await ns.trigger_event('custom_message', 'sid', {'data': 'data'}) assert result['result'] == ('sid', {'data': 'data'}) - def test_event_not_found_client(self): + async def test_event_not_found_client(self): result = {} class MyNamespace(async_namespace.AsyncClientNamespace): @@ -283,57 +275,56 @@ async def on_custom_message(self, sid, data): ns = MyNamespace('/foo') ns._set_client(mock.MagicMock()) - _run( - ns.trigger_event('another_custom_message', 'sid', {'data': 'data'}) - ) + await ns.trigger_event('another_custom_message', 'sid', + {'data': 'data'}) assert result == {} - def test_emit_client(self): + async def test_emit_client(self): ns = async_namespace.AsyncClientNamespace('/foo') mock_client = mock.MagicMock() mock_client.emit = mock.AsyncMock() ns._set_client(mock_client) - _run(ns.emit('ev', data='data', callback='cb')) + await ns.emit('ev', data='data', callback='cb') ns.client.emit.assert_awaited_with( 'ev', data='data', namespace='/foo', callback='cb' ) - _run(ns.emit('ev', data='data', namespace='/bar', callback='cb')) + await ns.emit('ev', data='data', namespace='/bar', callback='cb') ns.client.emit.assert_awaited_with( 'ev', data='data', namespace='/bar', callback='cb' ) - def test_send_client(self): + async def test_send_client(self): ns = async_namespace.AsyncClientNamespace('/foo') mock_client = mock.MagicMock() mock_client.send = mock.AsyncMock() ns._set_client(mock_client) - _run(ns.send(data='data', callback='cb')) + await ns.send(data='data', callback='cb') ns.client.send.assert_awaited_with( 'data', namespace='/foo', callback='cb' ) - _run(ns.send(data='data', namespace='/bar', callback='cb')) + await ns.send(data='data', namespace='/bar', callback='cb') ns.client.send.assert_awaited_with( 'data', namespace='/bar', callback='cb' ) - def test_call_client(self): + async def test_call_client(self): ns = async_namespace.AsyncClientNamespace('/foo') mock_client = mock.MagicMock() mock_client.call = mock.AsyncMock() ns._set_client(mock_client) - _run(ns.call('ev', data='data')) + await ns.call('ev', data='data') ns.client.call.assert_awaited_with( 'ev', data='data', namespace='/foo', timeout=None ) - _run(ns.call('ev', data='data', namespace='/bar', timeout=45)) + await ns.call('ev', data='data', namespace='/bar', timeout=45) ns.client.call.assert_awaited_with( 'ev', data='data', namespace='/bar', timeout=45 ) - def test_disconnect_client(self): + async def test_disconnect_client(self): ns = async_namespace.AsyncClientNamespace('/foo') mock_client = mock.MagicMock() mock_client.disconnect = mock.AsyncMock() ns._set_client(mock_client) - _run(ns.disconnect()) + await ns.disconnect() ns.client.disconnect.assert_awaited_with() diff --git a/tests/async/test_pubsub_manager.py b/tests/async/test_pubsub_manager.py index 46928827..71d948a6 100644 --- a/tests/async/test_pubsub_manager.py +++ b/tests/async/test_pubsub_manager.py @@ -7,7 +7,6 @@ from socketio import async_manager from socketio import async_pubsub_manager from socketio import packet -from .helpers import _run class TestAsyncPubSubManager: @@ -31,18 +30,18 @@ def generate_id(): self.pm.host_id = '123456' self.pm.initialize() - def test_default_init(self): + async def test_default_init(self): assert self.pm.channel == 'socketio' self.pm.server.start_background_task.assert_called_once_with( self.pm._thread ) - def test_custom_init(self): + async def test_custom_init(self): pubsub = async_pubsub_manager.AsyncPubSubManager(channel='foo') assert pubsub.channel == 'foo' assert len(pubsub.host_id) == 32 - def test_write_only_init(self): + async def test_write_only_init(self): mock_server = mock.MagicMock() pm = async_pubsub_manager.AsyncPubSubManager(write_only=True) pm.set_server(mock_server) @@ -51,8 +50,8 @@ def test_write_only_init(self): assert len(pm.host_id) == 32 assert pm.server.start_background_task.call_count == 0 - def test_emit(self): - _run(self.pm.emit('foo', 'bar')) + async def test_emit(self): + await self.pm.emit('foo', 'bar') self.pm._publish.assert_awaited_once_with( { 'method': 'emit', @@ -66,9 +65,9 @@ def test_emit(self): } ) - def test_emit_with_to(self): + async def test_emit_with_to(self): sid = 'room-mate' - _run(self.pm.emit('foo', 'bar', to=sid)) + await self.pm.emit('foo', 'bar', to=sid) self.pm._publish.assert_awaited_once_with( { 'method': 'emit', @@ -82,8 +81,8 @@ def test_emit_with_to(self): } ) - def test_emit_with_namespace(self): - _run(self.pm.emit('foo', 'bar', namespace='/baz')) + async def test_emit_with_namespace(self): + await self.pm.emit('foo', 'bar', namespace='/baz') self.pm._publish.assert_awaited_once_with( { 'method': 'emit', @@ -97,8 +96,8 @@ def test_emit_with_namespace(self): } ) - def test_emit_with_room(self): - _run(self.pm.emit('foo', 'bar', room='baz')) + async def test_emit_with_room(self): + await self.pm.emit('foo', 'bar', room='baz') self.pm._publish.assert_awaited_once_with( { 'method': 'emit', @@ -112,8 +111,8 @@ def test_emit_with_room(self): } ) - def test_emit_with_skip_sid(self): - _run(self.pm.emit('foo', 'bar', skip_sid='baz')) + async def test_emit_with_skip_sid(self): + await self.pm.emit('foo', 'bar', skip_sid='baz') self.pm._publish.assert_awaited_once_with( { 'method': 'emit', @@ -127,11 +126,11 @@ def test_emit_with_skip_sid(self): } ) - def test_emit_with_callback(self): + async def test_emit_with_callback(self): with mock.patch.object( self.pm, '_generate_ack_id', return_value='123' ): - _run(self.pm.emit('foo', 'bar', room='baz', callback='cb')) + await self.pm.emit('foo', 'bar', room='baz', callback='cb') self.pm._publish.assert_awaited_once_with( { 'method': 'emit', @@ -145,24 +144,22 @@ def test_emit_with_callback(self): } ) - def test_emit_with_callback_without_server(self): + async def test_emit_with_callback_without_server(self): standalone_pm = async_pubsub_manager.AsyncPubSubManager() with pytest.raises(RuntimeError): - _run(standalone_pm.emit('foo', 'bar', callback='cb')) + await standalone_pm.emit('foo', 'bar', callback='cb') - def test_emit_with_callback_missing_room(self): + async def test_emit_with_callback_missing_room(self): with mock.patch.object( self.pm, '_generate_ack_id', return_value='123' ): with pytest.raises(ValueError): - _run(self.pm.emit('foo', 'bar', callback='cb')) + await self.pm.emit('foo', 'bar', callback='cb') - def test_emit_with_ignore_queue(self): - sid = _run(self.pm.connect('123', '/')) - _run( - self.pm.emit( - 'foo', 'bar', room=sid, namespace='/', ignore_queue=True - ) + async def test_emit_with_ignore_queue(self): + sid = await self.pm.connect('123', '/') + await self.pm.emit( + 'foo', 'bar', room=sid, namespace='/', ignore_queue=True ) self.pm._publish.assert_not_awaited() assert self.pm.server._send_eio_packet.await_count == 1 @@ -171,33 +168,33 @@ def test_emit_with_ignore_queue(self): pkt = self.pm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42["foo","bar"]' - def test_can_disconnect(self): - sid = _run(self.pm.connect('123', '/')) - assert _run(self.pm.can_disconnect(sid, '/')) is True - _run(self.pm.can_disconnect(sid, '/foo')) + async def test_can_disconnect(self): + sid = await self.pm.connect('123', '/') + assert await self.pm.can_disconnect(sid, '/') is True + await self.pm.can_disconnect(sid, '/foo') self.pm._publish.assert_awaited_once_with( {'method': 'disconnect', 'sid': sid, 'namespace': '/foo', 'host_id': '123456'} ) - def test_disconnect(self): - _run(self.pm.disconnect('foo', '/')) + async def test_disconnect(self): + await self.pm.disconnect('foo', '/') self.pm._publish.assert_awaited_once_with( {'method': 'disconnect', 'sid': 'foo', 'namespace': '/', 'host_id': '123456'} ) - def test_disconnect_ignore_queue(self): - sid = _run(self.pm.connect('123', '/')) + async def test_disconnect_ignore_queue(self): + sid = await self.pm.connect('123', '/') self.pm.pre_disconnect(sid, '/') - _run(self.pm.disconnect(sid, '/', ignore_queue=True)) + await self.pm.disconnect(sid, '/', ignore_queue=True) self.pm._publish.assert_not_awaited() assert self.pm.is_connected(sid, '/') is False - def test_enter_room(self): - sid = _run(self.pm.connect('123', '/')) - _run(self.pm.enter_room(sid, '/', 'foo')) - _run(self.pm.enter_room('456', '/', 'foo')) + async def test_enter_room(self): + sid = await self.pm.connect('123', '/') + await self.pm.enter_room(sid, '/', 'foo') + await self.pm.enter_room('456', '/', 'foo') assert sid in self.pm.rooms['/']['foo'] assert self.pm.rooms['/']['foo'][sid] == '123' self.pm._publish.assert_awaited_once_with( @@ -205,35 +202,35 @@ def test_enter_room(self): 'namespace': '/', 'host_id': '123456'} ) - def test_leave_room(self): - sid = _run(self.pm.connect('123', '/')) - _run(self.pm.leave_room(sid, '/', 'foo')) - _run(self.pm.leave_room('456', '/', 'foo')) + async def test_leave_room(self): + sid = await self.pm.connect('123', '/') + await self.pm.leave_room(sid, '/', 'foo') + await self.pm.leave_room('456', '/', 'foo') assert 'foo' not in self.pm.rooms['/'] self.pm._publish.assert_awaited_once_with( {'method': 'leave_room', 'sid': '456', 'room': 'foo', 'namespace': '/', 'host_id': '123456'} ) - def test_close_room(self): - _run(self.pm.close_room('foo')) + async def test_close_room(self): + await self.pm.close_room('foo') self.pm._publish.assert_awaited_once_with( {'method': 'close_room', 'room': 'foo', 'namespace': '/', 'host_id': '123456'} ) - def test_close_room_with_namespace(self): - _run(self.pm.close_room('foo', '/bar')) + async def test_close_room_with_namespace(self): + await self.pm.close_room('foo', '/bar') self.pm._publish.assert_awaited_once_with( {'method': 'close_room', 'room': 'foo', 'namespace': '/bar', 'host_id': '123456'} ) - def test_handle_emit(self): + async def test_handle_emit(self): with mock.patch.object( async_manager.AsyncManager, 'emit' ) as super_emit: - _run(self.pm._handle_emit({'event': 'foo', 'data': 'bar'})) + await self.pm._handle_emit({'event': 'foo', 'data': 'bar'}) super_emit.assert_awaited_once_with( 'foo', 'bar', @@ -243,14 +240,12 @@ def test_handle_emit(self): callback=None, ) - def test_handle_emit_with_namespace(self): + async def test_handle_emit_with_namespace(self): with mock.patch.object( async_manager.AsyncManager, 'emit' ) as super_emit: - _run( - self.pm._handle_emit( - {'event': 'foo', 'data': 'bar', 'namespace': '/baz'} - ) + await self.pm._handle_emit( + {'event': 'foo', 'data': 'bar', 'namespace': '/baz'} ) super_emit.assert_awaited_once_with( 'foo', @@ -261,14 +256,12 @@ def test_handle_emit_with_namespace(self): callback=None, ) - def test_handle_emit_with_room(self): + async def test_handle_emit_with_room(self): with mock.patch.object( async_manager.AsyncManager, 'emit' ) as super_emit: - _run( - self.pm._handle_emit( - {'event': 'foo', 'data': 'bar', 'room': 'baz'} - ) + await self.pm._handle_emit( + {'event': 'foo', 'data': 'bar', 'room': 'baz'} ) super_emit.assert_awaited_once_with( 'foo', @@ -279,14 +272,12 @@ def test_handle_emit_with_room(self): callback=None, ) - def test_handle_emit_with_skip_sid(self): + async def test_handle_emit_with_skip_sid(self): with mock.patch.object( async_manager.AsyncManager, 'emit' ) as super_emit: - _run( - self.pm._handle_emit( - {'event': 'foo', 'data': 'bar', 'skip_sid': '123'} - ) + await self.pm._handle_emit( + {'event': 'foo', 'data': 'bar', 'skip_sid': '123'} ) super_emit.assert_awaited_once_with( 'foo', @@ -297,20 +288,18 @@ def test_handle_emit_with_skip_sid(self): callback=None, ) - def test_handle_emit_with_remote_callback(self): + async def test_handle_emit_with_remote_callback(self): with mock.patch.object( async_manager.AsyncManager, 'emit' ) as super_emit: - _run( - self.pm._handle_emit( - { - 'event': 'foo', - 'data': 'bar', - 'namespace': '/baz', - 'callback': ('sid', '/baz', 123), - 'host_id': 'x', - } - ) + await self.pm._handle_emit( + { + 'event': 'foo', + 'data': 'bar', + 'namespace': '/baz', + 'callback': ('sid', '/baz', 123), + 'host_id': 'x', + } ) assert super_emit.await_count == 1 assert super_emit.await_args[0] == ('foo', 'bar') @@ -320,7 +309,7 @@ def test_handle_emit_with_remote_callback(self): assert isinstance( super_emit.await_args[1]['callback'], functools.partial ) - _run(super_emit.await_args[1]['callback']('one', 2, 'three')) + await super_emit.await_args[1]['callback']('one', 2, 'three') self.pm._publish.assert_awaited_once_with( { 'method': 'callback', @@ -332,20 +321,18 @@ def test_handle_emit_with_remote_callback(self): } ) - def test_handle_emit_with_local_callback(self): + async def test_handle_emit_with_local_callback(self): with mock.patch.object( async_manager.AsyncManager, 'emit' ) as super_emit: - _run( - self.pm._handle_emit( - { - 'event': 'foo', - 'data': 'bar', - 'namespace': '/baz', - 'callback': ('sid', '/baz', 123), - 'host_id': self.pm.host_id, - } - ) + await self.pm._handle_emit( + { + 'event': 'foo', + 'data': 'bar', + 'namespace': '/baz', + 'callback': ('sid', '/baz', 123), + 'host_id': self.pm.host_id, + } ) assert super_emit.await_count == 1 assert super_emit.await_args[0] == ('foo', 'bar') @@ -355,163 +342,137 @@ def test_handle_emit_with_local_callback(self): assert isinstance( super_emit.await_args[1]['callback'], functools.partial ) - _run(super_emit.await_args[1]['callback']('one', 2, 'three')) + await super_emit.await_args[1]['callback']('one', 2, 'three') self.pm._publish.assert_not_awaited() - def test_handle_callback(self): + async def test_handle_callback(self): host_id = self.pm.host_id with mock.patch.object( self.pm, 'trigger_callback' ) as trigger: - _run( - self.pm._handle_callback( - { - 'method': 'callback', - 'host_id': host_id, - 'sid': 'sid', - 'namespace': '/', - 'id': 123, - 'args': ('one', 2), - } - ) + await self.pm._handle_callback( + { + 'method': 'callback', + 'host_id': host_id, + 'sid': 'sid', + 'namespace': '/', + 'id': 123, + 'args': ('one', 2), + } ) trigger.assert_awaited_once_with('sid', 123, ('one', 2)) - def test_handle_callback_bad_host_id(self): + async def test_handle_callback_bad_host_id(self): with mock.patch.object( self.pm, 'trigger_callback' ) as trigger: - _run( - self.pm._handle_callback( - { - 'method': 'callback', - 'host_id': 'bad', - 'sid': 'sid', - 'namespace': '/', - 'id': 123, - 'args': ('one', 2), - } - ) + await self.pm._handle_callback( + { + 'method': 'callback', + 'host_id': 'bad', + 'sid': 'sid', + 'namespace': '/', + 'id': 123, + 'args': ('one', 2), + } ) assert trigger.await_count == 0 - def test_handle_callback_missing_args(self): + async def test_handle_callback_missing_args(self): host_id = self.pm.host_id with mock.patch.object( self.pm, 'trigger_callback' ) as trigger: - _run( - self.pm._handle_callback( - { - 'method': 'callback', - 'host_id': host_id, - 'sid': 'sid', - 'namespace': '/', - 'id': 123, - } - ) + await self.pm._handle_callback( + { + 'method': 'callback', + 'host_id': host_id, + 'sid': 'sid', + 'namespace': '/', + 'id': 123, + } ) - _run( - self.pm._handle_callback( - { - 'method': 'callback', - 'host_id': host_id, - 'sid': 'sid', - 'namespace': '/', - } - ) + await self.pm._handle_callback( + { + 'method': 'callback', + 'host_id': host_id, + 'sid': 'sid', + 'namespace': '/', + } ) - _run( - self.pm._handle_callback( - {'method': 'callback', 'host_id': host_id, 'sid': 'sid'} - ) + await self.pm._handle_callback( + {'method': 'callback', 'host_id': host_id, 'sid': 'sid'} ) - _run( - self.pm._handle_callback( - {'method': 'callback', 'host_id': host_id} - ) + await self.pm._handle_callback( + {'method': 'callback', 'host_id': host_id} ) assert trigger.await_count == 0 - def test_handle_disconnect(self): - _run( - self.pm._handle_disconnect( - {'method': 'disconnect', 'sid': '123', 'namespace': '/foo'} - ) + async def test_handle_disconnect(self): + await self.pm._handle_disconnect( + {'method': 'disconnect', 'sid': '123', 'namespace': '/foo'} ) self.pm.server.disconnect.assert_awaited_once_with( sid='123', namespace='/foo', ignore_queue=True ) - def test_handle_enter_room(self): - sid = _run(self.pm.connect('123', '/')) + async def test_handle_enter_room(self): + sid = await self.pm.connect('123', '/') with mock.patch.object( async_manager.AsyncManager, 'enter_room' ) as super_enter_room: - _run( - self.pm._handle_enter_room( - {'method': 'enter_room', 'sid': sid, 'namespace': '/', - 'room': 'foo'} - ) + await self.pm._handle_enter_room( + {'method': 'enter_room', 'sid': sid, 'namespace': '/', + 'room': 'foo'} ) - _run( - self.pm._handle_enter_room( - {'method': 'enter_room', 'sid': '456', 'namespace': '/', - 'room': 'foo'} - ) + await self.pm._handle_enter_room( + {'method': 'enter_room', 'sid': '456', 'namespace': '/', + 'room': 'foo'} ) super_enter_room.assert_awaited_once_with(sid, '/', 'foo') - def test_handle_leave_room(self): - sid = _run(self.pm.connect('123', '/')) + async def test_handle_leave_room(self): + sid = await self.pm.connect('123', '/') with mock.patch.object( async_manager.AsyncManager, 'leave_room' ) as super_leave_room: - _run( - self.pm._handle_leave_room( - {'method': 'leave_room', 'sid': sid, 'namespace': '/', - 'room': 'foo'} - ) + await self.pm._handle_leave_room( + {'method': 'leave_room', 'sid': sid, 'namespace': '/', + 'room': 'foo'} ) - _run( - self.pm._handle_leave_room( - {'method': 'leave_room', 'sid': '456', 'namespace': '/', - 'room': 'foo'} - ) + await self.pm._handle_leave_room( + {'method': 'leave_room', 'sid': '456', 'namespace': '/', + 'room': 'foo'} ) super_leave_room.assert_awaited_once_with(sid, '/', 'foo') - def test_handle_close_room(self): + async def test_handle_close_room(self): with mock.patch.object( async_manager.AsyncManager, 'close_room' ) as super_close_room: - _run( - self.pm._handle_close_room( - {'method': 'close_room', 'room': 'foo'} - ) + await self.pm._handle_close_room( + {'method': 'close_room', 'room': 'foo'} ) super_close_room.assert_awaited_once_with( room='foo', namespace=None ) - def test_handle_close_room_with_namespace(self): + async def test_handle_close_room_with_namespace(self): with mock.patch.object( async_manager.AsyncManager, 'close_room' ) as super_close_room: - _run( - self.pm._handle_close_room( - { - 'method': 'close_room', - 'room': 'foo', - 'namespace': '/bar', - } - ) + await self.pm._handle_close_room( + { + 'method': 'close_room', + 'room': 'foo', + 'namespace': '/bar', + } ) super_close_room.assert_awaited_once_with( room='foo', namespace='/bar' ) - def test_background_thread(self): + async def test_background_thread(self): self.pm._handle_emit = mock.AsyncMock() self.pm._handle_callback = mock.AsyncMock() self.pm._handle_disconnect = mock.AsyncMock() @@ -548,7 +509,7 @@ async def messages(): 'host_id': host_id}) self.pm._listen = messages - _run(self.pm._thread()) + await self.pm._thread() self.pm._handle_emit.assert_awaited_once_with( {'method': 'emit', 'value': 'foo', 'host_id': 'x'} @@ -575,7 +536,7 @@ async def messages(): {'method': 'close_room', 'value': 'baz', 'host_id': 'x'} ) - def test_background_thread_exception(self): + async def test_background_thread_exception(self): self.pm._handle_emit = mock.AsyncMock(side_effect=[ ValueError(), asyncio.CancelledError]) @@ -584,7 +545,7 @@ async def messages(): yield {'method': 'emit', 'value': 'bar', 'host_id': 'x'} self.pm._listen = messages - _run(self.pm._thread()) + await self.pm._thread() self.pm._handle_emit.assert_any_await( {'method': 'emit', 'value': 'foo', 'host_id': 'x'} diff --git a/tests/async/test_server.py b/tests/async/test_server.py index b2de48a9..d9129d46 100644 --- a/tests/async/test_server.py +++ b/tests/async/test_server.py @@ -11,7 +11,6 @@ from socketio import exceptions from socketio import namespace from socketio import packet -from .helpers import _run @mock.patch('socketio.server.engineio.AsyncServer', **{ @@ -32,25 +31,25 @@ def _get_mock_manager(self): mgr.trigger_callback = mock.AsyncMock() return mgr - def test_create(self, eio): + async def test_create(self, eio): eio.return_value.handle_request = mock.AsyncMock() mgr = self._get_mock_manager() s = async_server.AsyncServer( client_manager=mgr, async_handlers=True, foo='bar' ) - _run(s.handle_request({})) - _run(s.handle_request({})) + await s.handle_request({}) + await s.handle_request({}) eio.assert_called_once_with(**{'foo': 'bar', 'async_handlers': False}) assert s.manager == mgr assert s.eio.on.call_count == 3 assert s.async_handlers - def test_attach(self, eio): + async def test_attach(self, eio): s = async_server.AsyncServer() s.attach('app', 'path') eio.return_value.attach.assert_called_once_with('app', 'path') - def test_on_event(self, eio): + async def test_on_event(self, eio): s = async_server.AsyncServer() @s.on('connect') @@ -67,18 +66,16 @@ def bar(): assert s.handlers['/']['disconnect'] == bar assert s.handlers['/foo']['disconnect'] == bar - def test_emit(self, eio): + async def test_emit(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - _run( - s.emit( - 'my event', - {'foo': 'bar'}, - to='room', - skip_sid='123', - namespace='/foo', - callback='cb', - ) + await s.emit( + 'my event', + {'foo': 'bar'}, + to='room', + skip_sid='123', + namespace='/foo', + callback='cb', ) s.manager.emit.assert_awaited_once_with( 'my event', @@ -89,16 +86,14 @@ def test_emit(self, eio): callback='cb', ignore_queue=False, ) - _run( - s.emit( - 'my event', - {'foo': 'bar'}, - room='room', - skip_sid='123', - namespace='/foo', - callback='cb', - ignore_queue=True, - ) + await s.emit( + 'my event', + {'foo': 'bar'}, + room='room', + skip_sid='123', + namespace='/foo', + callback='cb', + ignore_queue=True, ) s.manager.emit.assert_awaited_with( 'my event', @@ -110,17 +105,15 @@ def test_emit(self, eio): ignore_queue=True, ) - def test_emit_default_namespace(self, eio): + async def test_emit_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - _run( - s.emit( - 'my event', - {'foo': 'bar'}, - to='room', - skip_sid='123', - callback='cb', - ) + await s.emit( + 'my event', + {'foo': 'bar'}, + to='room', + skip_sid='123', + callback='cb', ) s.manager.emit.assert_awaited_once_with( 'my event', @@ -131,15 +124,13 @@ def test_emit_default_namespace(self, eio): callback='cb', ignore_queue=False, ) - _run( - s.emit( - 'my event', - {'foo': 'bar'}, - room='room', - skip_sid='123', - callback='cb', - ignore_queue=True, - ) + await s.emit( + 'my event', + {'foo': 'bar'}, + room='room', + skip_sid='123', + callback='cb', + ignore_queue=True, ) s.manager.emit.assert_awaited_with( 'my event', @@ -151,17 +142,15 @@ def test_emit_default_namespace(self, eio): ignore_queue=True, ) - def test_send(self, eio): + async def test_send(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - _run( - s.send( - 'foo', - to='room', - skip_sid='123', - namespace='/foo', - callback='cb', - ) + await s.send( + 'foo', + to='room', + skip_sid='123', + namespace='/foo', + callback='cb', ) s.manager.emit.assert_awaited_once_with( 'message', @@ -172,15 +161,13 @@ def test_send(self, eio): callback='cb', ignore_queue=False, ) - _run( - s.send( - 'foo', - room='room', - skip_sid='123', - namespace='/foo', - callback='cb', - ignore_queue=True, - ) + await s.send( + 'foo', + room='room', + skip_sid='123', + namespace='/foo', + callback='cb', + ignore_queue=True, ) s.manager.emit.assert_awaited_with( 'message', @@ -192,7 +179,7 @@ def test_send(self, eio): ignore_queue=True, ) - def test_call(self, eio): + async def test_call(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) @@ -201,9 +188,9 @@ async def fake_event_wait(): return True s.eio.create_event.return_value.wait = fake_event_wait - assert _run(s.call('foo', sid='123')) == ('foo', 321) + assert await s.call('foo', sid='123') == ('foo', 321) - def test_call_with_timeout(self, eio): + async def test_call_with_timeout(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) @@ -212,253 +199,253 @@ async def fake_event_wait(): s.eio.create_event.return_value.wait = fake_event_wait with pytest.raises(exceptions.TimeoutError): - _run(s.call('foo', sid='123', timeout=0.01)) + await s.call('foo', sid='123', timeout=0.01) - def test_call_with_broadcast(self, eio): + async def test_call_with_broadcast(self, eio): s = async_server.AsyncServer() with pytest.raises(ValueError): - _run(s.call('foo')) + await s.call('foo') - def test_call_without_async_handlers(self, eio): + async def test_call_without_async_handlers(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer( client_manager=mgr, async_handlers=False ) with pytest.raises(RuntimeError): - _run(s.call('foo', sid='123', timeout=12)) + await s.call('foo', sid='123', timeout=12) - def test_enter_room(self, eio): + async def test_enter_room(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - _run(s.enter_room('123', 'room', namespace='/foo')) + await s.enter_room('123', 'room', namespace='/foo') s.manager.enter_room.assert_awaited_once_with('123', '/foo', 'room') - def test_enter_room_default_namespace(self, eio): + async def test_enter_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - _run(s.enter_room('123', 'room')) + await s.enter_room('123', 'room') s.manager.enter_room.assert_awaited_once_with('123', '/', 'room') - def test_leave_room(self, eio): + async def test_leave_room(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - _run(s.leave_room('123', 'room', namespace='/foo')) + await s.leave_room('123', 'room', namespace='/foo') s.manager.leave_room.assert_awaited_once_with('123', '/foo', 'room') - def test_leave_room_default_namespace(self, eio): + async def test_leave_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - _run(s.leave_room('123', 'room')) + await s.leave_room('123', 'room') s.manager.leave_room.assert_awaited_once_with('123', '/', 'room') - def test_close_room(self, eio): + async def test_close_room(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - _run(s.close_room('room', namespace='/foo')) + await s.close_room('room', namespace='/foo') s.manager.close_room.assert_awaited_once_with('room', '/foo') - def test_close_room_default_namespace(self, eio): + async def test_close_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - _run(s.close_room('room')) + await s.close_room('room') s.manager.close_room.assert_awaited_once_with('room', '/') - def test_rooms(self, eio): + async def test_rooms(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) s.rooms('123', namespace='/foo') s.manager.get_rooms.assert_called_once_with('123', '/foo') - def test_rooms_default_namespace(self, eio): + async def test_rooms_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) s.rooms('123') s.manager.get_rooms.assert_called_once_with('123', '/') - def test_handle_request(self, eio): + async def test_handle_request(self, eio): eio.return_value.handle_request = mock.AsyncMock() s = async_server.AsyncServer() - _run(s.handle_request('environ')) + await s.handle_request('environ') s.eio.handle_request.assert_awaited_once_with('environ') - def test_send_packet(self, eio): + async def test_send_packet(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - _run(s._send_packet('123', packet.Packet( - packet.EVENT, ['my event', 'my data'], namespace='/foo'))) + await s._send_packet('123', packet.Packet( + packet.EVENT, ['my event', 'my data'], namespace='/foo')) s.eio.send.assert_awaited_once_with( '123', '2/foo,["my event","my data"]' ) - def test_send_eio_packet(self, eio): + async def test_send_eio_packet(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - _run(s._send_eio_packet('123', eio_packet.Packet( - eio_packet.MESSAGE, 'hello'))) + await s._send_eio_packet('123', eio_packet.Packet( + eio_packet.MESSAGE, 'hello')) assert s.eio.send_packet.await_count == 1 assert s.eio.send_packet.await_args_list[0][0][0] == '123' pkt = s.eio.send_packet.await_args_list[0][0][1] assert pkt.encode() == '4hello' - def test_transport(self, eio): + async def test_transport(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.eio.transport = mock.MagicMock(return_value='polling') - sid_foo = _run(s.manager.connect('123', '/foo')) + sid_foo = await s.manager.connect('123', '/foo') assert s.transport(sid_foo, '/foo') == 'polling' s.eio.transport.assert_called_once_with('123') - def test_handle_connect(self, eio): + async def test_handle_connect(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock() s.on('connect', handler) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') assert s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_awaited_once_with('123', '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 - _run(s._handle_eio_connect('456', 'environ')) - _run(s._handle_eio_message('456', '0')) + await s._handle_eio_connect('456', 'environ') + await s._handle_eio_message('456', '0') assert s.manager.initialize.call_count == 1 - def test_handle_connect_with_auth(self, eio): + async def test_handle_connect_with_auth(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock() s.on('connect', handler) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0{"token":"abc"}')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0{"token":"abc"}') assert s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ', {'token': 'abc'}) s.eio.send.assert_awaited_once_with('123', '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 - _run(s._handle_eio_connect('456', 'environ')) - _run(s._handle_eio_message('456', '0')) + await s._handle_eio_connect('456', 'environ') + await s._handle_eio_message('456', '0') assert s.manager.initialize.call_count == 1 - def test_handle_connect_with_auth_none(self, eio): + async def test_handle_connect_with_auth_none(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock(side_effect=[TypeError, None, None]) s.on('connect', handler) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') assert s.manager.is_connected('1', '/') handler.assert_called_with('1', 'environ', None) s.eio.send.assert_awaited_once_with('123', '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 - _run(s._handle_eio_connect('456', 'environ')) - _run(s._handle_eio_message('456', '0')) + await s._handle_eio_connect('456', 'environ') + await s._handle_eio_message('456', '0') assert s.manager.initialize.call_count == 1 - def test_handle_connect_async(self, eio): + async def test_handle_connect_async(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() handler = mock.AsyncMock() s.on('connect', handler) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') assert s.manager.is_connected('1', '/') handler.assert_awaited_once_with('1', 'environ') s.eio.send.assert_awaited_once_with('123', '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 - _run(s._handle_eio_connect('456', 'environ')) - _run(s._handle_eio_message('456', '0')) + await s._handle_eio_connect('456', 'environ') + await s._handle_eio_message('456', '0') assert s.manager.initialize.call_count == 1 - def test_handle_connect_with_default_implied_namespaces(self, eio): + async def test_handle_connect_with_default_implied_namespaces(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') + await s._handle_eio_message('123', '0/foo,') assert s.manager.is_connected('1', '/') assert not s.manager.is_connected('2', '/foo') - def test_handle_connect_with_implied_namespaces(self, eio): + async def test_handle_connect_with_implied_namespaces(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(namespaces=['/foo']) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') + await s._handle_eio_message('123', '0/foo,') assert not s.manager.is_connected('1', '/') assert s.manager.is_connected('1', '/foo') - def test_handle_connect_with_all_implied_namespaces(self, eio): + async def test_handle_connect_with_all_implied_namespaces(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(namespaces='*') - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') + await s._handle_eio_message('123', '0/foo,') assert s.manager.is_connected('1', '/') assert s.manager.is_connected('2', '/foo') - def test_handle_connect_namespace(self, eio): + async def test_handle_connect_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock() s.on('connect', handler, namespace='/foo') - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') assert s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_awaited_once_with('123', '0/foo,{"sid":"1"}') - def test_handle_connect_always_connect(self, eio): + async def test_handle_connect_always_connect(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(always_connect=True) s.manager.initialize = mock.MagicMock() handler = mock.MagicMock() s.on('connect', handler) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') assert s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_awaited_once_with('123', '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 - _run(s._handle_eio_connect('456', 'environ')) - _run(s._handle_eio_message('456', '0')) + await s._handle_eio_connect('456', 'environ') + await s._handle_eio_message('456', '0') assert s.manager.initialize.call_count == 1 - def test_handle_connect_rejected(self, eio): + async def test_handle_connect_rejected(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock(return_value=False) s.on('connect', handler) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') assert not s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_awaited_once_with( '123', '4{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} - def test_handle_connect_namespace_rejected(self, eio): + async def test_handle_connect_namespace_rejected(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock(return_value=False) s.on('connect', handler, namespace='/foo') - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') assert not s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_any_await( '123', '4/foo,{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} - def test_handle_connect_rejected_always_connect(self, eio): + async def test_handle_connect_rejected_always_connect(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(always_connect=True) handler = mock.MagicMock(return_value=False) s.on('connect', handler) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') assert not s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_any_await('123', '0{"sid":"1"}') @@ -466,13 +453,13 @@ def test_handle_connect_rejected_always_connect(self, eio): '123', '1{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} - def test_handle_connect_namespace_rejected_always_connect(self, eio): + async def test_handle_connect_namespace_rejected_always_connect(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(always_connect=True) handler = mock.MagicMock(return_value=False) s.on('connect', handler, namespace='/foo') - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') assert not s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_any_await('123', '0/foo,{"sid":"1"}') @@ -480,37 +467,37 @@ def test_handle_connect_namespace_rejected_always_connect(self, eio): '123', '1/foo,{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} - def test_handle_connect_rejected_with_exception(self, eio): + async def test_handle_connect_rejected_with_exception(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock( side_effect=exceptions.ConnectionRefusedError('fail_reason') ) s.on('connect', handler) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') assert not s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_awaited_once_with( '123', '4{"message":"fail_reason"}') assert s.environ == {'123': 'environ'} - def test_handle_connect_rejected_with_empty_exception(self, eio): + async def test_handle_connect_rejected_with_empty_exception(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock( side_effect=exceptions.ConnectionRefusedError() ) s.on('connect', handler) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') assert not s.manager.is_connected('1', '/') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_awaited_once_with( '123', '4{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} - def test_handle_connect_namespace_rejected_with_exception(self, eio): + async def test_handle_connect_namespace_rejected_with_exception(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock( @@ -518,109 +505,110 @@ def test_handle_connect_namespace_rejected_with_exception(self, eio): 'fail_reason', 1, '2') ) s.on('connect', handler, namespace='/foo') - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') assert not s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_awaited_once_with( '123', '4/foo,{"message":"fail_reason","data":[1,"2"]}') assert s.environ == {'123': 'environ'} - def test_handle_connect_namespace_rejected_with_empty_exception(self, eio): + async def test_handle_connect_namespace_rejected_with_empty_exception( + self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock( side_effect=exceptions.ConnectionRefusedError() ) s.on('connect', handler, namespace='/foo') - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') assert not s.manager.is_connected('1', '/foo') handler.assert_called_once_with('1', 'environ') s.eio.send.assert_awaited_once_with( '123', '4/foo,{"message":"Connection rejected by server"}') assert s.environ == {'123': 'environ'} - def test_handle_disconnect(self, eio): + async def test_handle_disconnect(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.manager.disconnect = mock.AsyncMock() handler = mock.MagicMock() s.on('disconnect', handler) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) - _run(s._handle_eio_disconnect('123')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') + await s._handle_eio_disconnect('123') handler.assert_called_once_with('1') s.manager.disconnect.assert_awaited_once_with( '1', '/', ignore_queue=True) assert s.environ == {} - def test_handle_disconnect_namespace(self, eio): + async def test_handle_disconnect_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock() s.on('disconnect', handler) handler_namespace = mock.MagicMock() s.on('disconnect', handler_namespace, namespace='/foo') - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) - _run(s._handle_eio_disconnect('123')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') + await s._handle_eio_disconnect('123') handler.assert_not_called() handler_namespace.assert_called_once_with('1') assert s.environ == {} - def test_handle_disconnect_only_namespace(self, eio): + async def test_handle_disconnect_only_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock() s.on('disconnect', handler) handler_namespace = mock.MagicMock() s.on('disconnect', handler_namespace, namespace='/foo') - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) - _run(s._handle_eio_message('123', '1/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') + await s._handle_eio_message('123', '1/foo,') assert handler.call_count == 0 handler_namespace.assert_called_once_with('1') assert s.environ == {'123': 'environ'} - def test_handle_disconnect_unknown_client(self, eio): + async def test_handle_disconnect_unknown_client(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - _run(s._handle_eio_disconnect('123')) + await s._handle_eio_disconnect('123') - def test_handle_event(self, eio): + async def test_handle_event(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = _run(s.manager.connect('123', '/')) + sid = await s.manager.connect('123', '/') handler = mock.AsyncMock() catchall_handler = mock.AsyncMock() s.on('msg', handler) s.on('*', catchall_handler) - _run(s._handle_eio_message('123', '2["msg","a","b"]')) - _run(s._handle_eio_message('123', '2["my message","a","b","c"]')) + await s._handle_eio_message('123', '2["msg","a","b"]') + await s._handle_eio_message('123', '2["my message","a","b","c"]') handler.assert_awaited_once_with(sid, 'a', 'b') catchall_handler.assert_awaited_once_with( 'my message', sid, 'a', 'b', 'c') - def test_handle_event_with_namespace(self, eio): + async def test_handle_event_with_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = _run(s.manager.connect('123', '/foo')) + sid = await s.manager.connect('123', '/foo') handler = mock.MagicMock() catchall_handler = mock.MagicMock() s.on('msg', handler, namespace='/foo') s.on('*', catchall_handler, namespace='/foo') - _run(s._handle_eio_message('123', '2/foo,["msg","a","b"]')) - _run(s._handle_eio_message('123', '2/foo,["my message","a","b","c"]')) + await s._handle_eio_message('123', '2/foo,["msg","a","b"]') + await s._handle_eio_message('123', '2/foo,["my message","a","b","c"]') handler.assert_called_once_with(sid, 'a', 'b') catchall_handler.assert_called_once_with( 'my message', sid, 'a', 'b', 'c') - def test_handle_event_with_catchall_namespace(self, eio): + async def test_handle_event_with_catchall_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid_foo = _run(s.manager.connect('123', '/foo')) - sid_bar = _run(s.manager.connect('123', '/bar')) + sid_foo = await s.manager.connect('123', '/foo') + sid_bar = await s.manager.connect('123', '/bar') connect_star_handler = mock.MagicMock() msg_foo_handler = mock.MagicMock() msg_star_handler = mock.MagicMock() @@ -631,12 +619,12 @@ def test_handle_event_with_catchall_namespace(self, eio): s.on('msg', msg_star_handler, namespace='*') s.on('*', star_foo_handler, namespace='/foo') s.on('*', star_star_handler, namespace='*') - _run(s._trigger_event('connect', '/bar', sid_bar)) - _run(s._handle_eio_message('123', '2/foo,["msg","a","b"]')) - _run(s._handle_eio_message('123', '2/bar,["msg","a","b"]')) - _run(s._handle_eio_message('123', '2/foo,["my message","a","b","c"]')) - _run(s._handle_eio_message('123', '2/bar,["my message","a","b","c"]')) - _run(s._trigger_event('disconnect', '/bar', sid_bar)) + await s._trigger_event('connect', '/bar', sid_bar) + await s._handle_eio_message('123', '2/foo,["msg","a","b"]') + await s._handle_eio_message('123', '2/bar,["msg","a","b"]') + await s._handle_eio_message('123', '2/foo,["my message","a","b","c"]') + await s._handle_eio_message('123', '2/bar,["my message","a","b","c"]') + await s._trigger_event('disconnect', '/bar', sid_bar) connect_star_handler.assert_called_once_with('/bar', sid_bar) msg_foo_handler.assert_called_once_with(sid_foo, 'a', 'b') msg_star_handler.assert_called_once_with('/bar', sid_bar, 'a', 'b') @@ -645,157 +633,151 @@ def test_handle_event_with_catchall_namespace(self, eio): star_star_handler.assert_called_once_with( 'my message', '/bar', sid_bar, 'a', 'b', 'c') - def test_handle_event_with_disconnected_namespace(self, eio): + async def test_handle_event_with_disconnected_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - _run(s.manager.connect('123', '/foo')) + await s.manager.connect('123', '/foo') handler = mock.MagicMock() s.on('my message', handler, namespace='/bar') - _run(s._handle_eio_message('123', '2/bar,["my message","a","b","c"]')) + await s._handle_eio_message('123', '2/bar,["my message","a","b","c"]') handler.assert_not_called() - def test_handle_event_binary(self, eio): + async def test_handle_event_binary(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = _run(s.manager.connect('123', '/')) + sid = await s.manager.connect('123', '/') handler = mock.MagicMock() s.on('my message', handler) - _run( - s._handle_eio_message( - '123', - '52-["my message","a",' - '{"_placeholder":true,"num":1},' - '{"_placeholder":true,"num":0}]', - ) + await s._handle_eio_message( + '123', + '52-["my message","a",' + '{"_placeholder":true,"num":1},' + '{"_placeholder":true,"num":0}]', ) - _run(s._handle_eio_message('123', b'foo')) - _run(s._handle_eio_message('123', b'bar')) + await s._handle_eio_message('123', b'foo') + await s._handle_eio_message('123', b'bar') handler.assert_called_once_with(sid, 'a', b'bar', b'foo') - def test_handle_event_binary_ack(self, eio): + async def test_handle_event_binary_ack(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) s.manager.trigger_callback = mock.AsyncMock() - sid = _run(s.manager.connect('123', '/')) - _run( - s._handle_eio_message( - '123', - '61-321["my message","a",' '{"_placeholder":true,"num":0}]', - ) + sid = await s.manager.connect('123', '/') + await s._handle_eio_message( + '123', + '61-321["my message","a",' '{"_placeholder":true,"num":0}]', ) - _run(s._handle_eio_message('123', b'foo')) + await s._handle_eio_message('123', b'foo') s.manager.trigger_callback.assert_awaited_once_with( sid, 321, ['my message', 'a', b'foo'] ) - def test_handle_event_with_ack(self, eio): + async def test_handle_event_with_ack(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = _run(s.manager.connect('123', '/')) + sid = await s.manager.connect('123', '/') handler = mock.MagicMock(return_value='foo') s.on('my message', handler) - _run(s._handle_eio_message('123', '21000["my message","foo"]')) + await s._handle_eio_message('123', '21000["my message","foo"]') handler.assert_called_once_with(sid, 'foo') s.eio.send.assert_awaited_once_with( '123', '31000["foo"]' ) - def test_handle_unknown_event_with_ack(self, eio): + async def test_handle_unknown_event_with_ack(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - _run(s.manager.connect('123', '/')) + await s.manager.connect('123', '/') handler = mock.MagicMock(return_value='foo') s.on('my message', handler) - _run(s._handle_eio_message('123', '21000["another message","foo"]')) + await s._handle_eio_message('123', '21000["another message","foo"]') s.eio.send.assert_not_awaited() - def test_handle_event_with_ack_none(self, eio): + async def test_handle_event_with_ack_none(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = _run(s.manager.connect('123', '/')) + sid = await s.manager.connect('123', '/') handler = mock.MagicMock(return_value=None) s.on('my message', handler) - _run(s._handle_eio_message('123', '21000["my message","foo"]')) + await s._handle_eio_message('123', '21000["my message","foo"]') handler.assert_called_once_with(sid, 'foo') s.eio.send.assert_awaited_once_with('123', '31000[]') - def test_handle_event_with_ack_tuple(self, eio): + async def test_handle_event_with_ack_tuple(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = _run(s.manager.connect('123', '/')) + sid = await s.manager.connect('123', '/') handler = mock.MagicMock(return_value=(1, '2', True)) s.on('my message', handler) - _run(s._handle_eio_message('123', '21000["my message","a","b","c"]')) + await s._handle_eio_message('123', '21000["my message","a","b","c"]') handler.assert_called_once_with(sid, 'a', 'b', 'c') s.eio.send.assert_awaited_once_with( '123', '31000[1,"2",true]' ) - def test_handle_event_with_ack_list(self, eio): + async def test_handle_event_with_ack_list(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = _run(s.manager.connect('123', '/')) + sid = await s.manager.connect('123', '/') handler = mock.MagicMock(return_value=[1, '2', True]) s.on('my message', handler) - _run(s._handle_eio_message('123', '21000["my message","a","b","c"]')) + await s._handle_eio_message('123', '21000["my message","a","b","c"]') handler.assert_called_once_with(sid, 'a', 'b', 'c') s.eio.send.assert_awaited_once_with( '123', '31000[[1,"2",true]]' ) - def test_handle_event_with_ack_binary(self, eio): + async def test_handle_event_with_ack_binary(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = _run(s.manager.connect('123', '/')) + sid = await s.manager.connect('123', '/') handler = mock.MagicMock(return_value=b'foo') s.on('my message', handler) - _run(s._handle_eio_message('123', '21000["my message","foo"]')) + await s._handle_eio_message('123', '21000["my message","foo"]') handler.assert_any_call(sid, 'foo') - def test_handle_error_packet(self, eio): + async def test_handle_error_packet(self, eio): s = async_server.AsyncServer() with pytest.raises(ValueError): - _run(s._handle_eio_message('123', '4')) + await s._handle_eio_message('123', '4') - def test_handle_invalid_packet(self, eio): + async def test_handle_invalid_packet(self, eio): s = async_server.AsyncServer() with pytest.raises(ValueError): - _run(s._handle_eio_message('123', '9')) + await s._handle_eio_message('123', '9') - def test_send_with_ack(self, eio): + async def test_send_with_ack(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/'] = {} - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') cb = mock.MagicMock() id1 = s.manager._generate_ack_id('1', cb) id2 = s.manager._generate_ack_id('1', cb) - _run(s._send_packet('123', packet.Packet( - packet.EVENT, ['my event', 'foo'], id=id1))) - _run(s._send_packet('123', packet.Packet( - packet.EVENT, ['my event', 'bar'], id=id2))) - _run(s._handle_eio_message('123', '31["foo",2]')) + await s._send_packet('123', packet.Packet( + packet.EVENT, ['my event', 'foo'], id=id1)) + await s._send_packet('123', packet.Packet( + packet.EVENT, ['my event', 'bar'], id=id2)) + await s._handle_eio_message('123', '31["foo",2]') cb.assert_called_once_with('foo', 2) - def test_send_with_ack_namespace(self, eio): + async def test_send_with_ack_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/foo'] = {} - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') cb = mock.MagicMock() id = s.manager._generate_ack_id('1', cb) - _run( - s._send_packet( - '123', packet.Packet(packet.EVENT, ['my event', 'foo'], - namespace='/foo', id=id) - ) + await s._send_packet( + '123', packet.Packet(packet.EVENT, ['my event', 'foo'], + namespace='/foo', id=id) ) - _run(s._handle_eio_message('123', '3/foo,1["foo",2]')) + await s._handle_eio_message('123', '3/foo,1["foo",2]') cb.assert_called_once_with('foo', 2) - def test_session(self, eio): + async def test_session(self, eio): fake_session = {} async def fake_get_session(eio_sid): @@ -836,65 +818,65 @@ async def _test(): '/ns': {'a': 'b'}, } - _run(_test()) + await _test() - def test_disconnect(self, eio): + async def test_disconnect(self, eio): eio.return_value.send = mock.AsyncMock() eio.return_value.disconnect = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/'] = {} - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) - _run(s.disconnect('1')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') + await s.disconnect('1') s.eio.send.assert_any_await('123', '1') assert not s.manager.is_connected('1', '/') - def test_disconnect_ignore_queue(self, eio): + async def test_disconnect_ignore_queue(self, eio): eio.return_value.send = mock.AsyncMock() eio.return_value.disconnect = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/'] = {} - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) - _run(s.disconnect('1', ignore_queue=True)) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') + await s.disconnect('1', ignore_queue=True) s.eio.send.assert_any_await('123', '1') assert not s.manager.is_connected('1', '/') - def test_disconnect_namespace(self, eio): + async def test_disconnect_namespace(self, eio): eio.return_value.send = mock.AsyncMock() eio.return_value.disconnect = mock.AsyncMock() s = async_server.AsyncServer() s.handlers['/foo'] = {} - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) - _run(s.disconnect('1', namespace='/foo')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') + await s.disconnect('1', namespace='/foo') s.eio.send.assert_any_await('123', '1/foo,') assert not s.manager.is_connected('1', '/foo') - def test_disconnect_twice(self, eio): + async def test_disconnect_twice(self, eio): eio.return_value.send = mock.AsyncMock() eio.return_value.disconnect = mock.AsyncMock() s = async_server.AsyncServer() - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0')) - _run(s.disconnect('1')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') + await s.disconnect('1') calls = s.eio.send.await_count assert not s.manager.is_connected('1', '/') - _run(s.disconnect('1')) + await s.disconnect('1') assert calls == s.eio.send.await_count - def test_disconnect_twice_namespace(self, eio): + async def test_disconnect_twice_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) - _run(s.disconnect('1', namespace='/foo')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') + await s.disconnect('1', namespace='/foo') calls = s.eio.send.await_count assert not s.manager.is_connected('1', '/foo') - _run(s.disconnect('1', namespace='/foo')) + await s.disconnect('1', namespace='/foo') assert calls == s.eio.send.await_count - def test_namespace_handler(self, eio): + async def test_namespace_handler(self, eio): eio.return_value.send = mock.AsyncMock() result = {} @@ -916,19 +898,19 @@ async def on_baz(self, sid, data1, data2): s = async_server.AsyncServer(async_handlers=False) s.register_namespace(MyNamespace('/foo')) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') assert result['result'] == ('1', 'environ') - _run(s._handle_eio_message('123', '2/foo,["foo","a"]')) + await s._handle_eio_message('123', '2/foo,["foo","a"]') assert result['result'] == ('1', 'a') - _run(s._handle_eio_message('123', '2/foo,["bar"]')) + await s._handle_eio_message('123', '2/foo,["bar"]') assert result['result'] == 'bar' - _run(s._handle_eio_message('123', '2/foo,["baz","a","b"]')) + await s._handle_eio_message('123', '2/foo,["baz","a","b"]') assert result['result'] == ('a', 'b') - _run(s.disconnect('1', '/foo')) + await s.disconnect('1', '/foo') assert result['result'] == ('disconnect', '1') - def test_catchall_namespace_handler(self, eio): + async def test_catchall_namespace_handler(self, eio): eio.return_value.send = mock.AsyncMock() result = {} @@ -950,19 +932,19 @@ async def on_baz(self, ns, sid, data1, data2): s = async_server.AsyncServer(async_handlers=False, namespaces='*') s.register_namespace(MyNamespace('*')) - _run(s._handle_eio_connect('123', 'environ')) - _run(s._handle_eio_message('123', '0/foo,')) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0/foo,') assert result['result'] == ('1', '/foo', 'environ') - _run(s._handle_eio_message('123', '2/foo,["foo","a"]')) + await s._handle_eio_message('123', '2/foo,["foo","a"]') assert result['result'] == ('1', '/foo', 'a') - _run(s._handle_eio_message('123', '2/foo,["bar"]')) + await s._handle_eio_message('123', '2/foo,["bar"]') assert result['result'] == 'bar/foo' - _run(s._handle_eio_message('123', '2/foo,["baz","a","b"]')) + await s._handle_eio_message('123', '2/foo,["baz","a","b"]') assert result['result'] == ('/foo', 'a', 'b') - _run(s.disconnect('1', '/foo')) + await s.disconnect('1', '/foo') assert result['result'] == ('disconnect', '1', '/foo') - def test_bad_namespace_handler(self, eio): + async def test_bad_namespace_handler(self, eio): class Dummy: pass @@ -981,7 +963,7 @@ class SyncNS(namespace.Namespace): with pytest.raises(ValueError): s.register_namespace(SyncNS()) - def test_logger(self, eio): + async def test_logger(self, eio): s = async_server.AsyncServer(logger=False) assert s.logger.getEffectiveLevel() == logging.ERROR s.logger.setLevel(logging.NOTSET) @@ -994,13 +976,13 @@ def test_logger(self, eio): s = async_server.AsyncServer(logger='foo') assert s.logger == 'foo' - def test_engineio_logger(self, eio): + async def test_engineio_logger(self, eio): async_server.AsyncServer(engineio_logger='foo') eio.assert_called_once_with( **{'logger': 'foo', 'async_handlers': False} ) - def test_custom_json(self, eio): + async def test_custom_json(self, eio): # Warning: this test cannot run in parallel with other tests, as it # changes the JSON encoding/decoding functions @@ -1029,10 +1011,10 @@ def loads(*args, **kwargs): # restore the default JSON module packet.Packet.json = json - def test_async_handlers(self, eio): + async def test_async_handlers(self, eio): s = async_server.AsyncServer(async_handlers=True) - _run(s.manager.connect('123', '/')) - _run(s._handle_eio_message('123', '2["my message","a","b","c"]')) + await s.manager.connect('123', '/') + await s._handle_eio_message('123', '2["my message","a","b","c"]') s.eio.start_background_task.assert_called_once_with( s._handle_event_internal, s, @@ -1043,21 +1025,21 @@ def test_async_handlers(self, eio): None, ) - def test_shutdown(self, eio): + async def test_shutdown(self, eio): s = async_server.AsyncServer() s.eio.shutdown = mock.AsyncMock() - _run(s.shutdown()) + await s.shutdown() s.eio.shutdown.assert_awaited_once_with() - def test_start_background_task(self, eio): + async def test_start_background_task(self, eio): s = async_server.AsyncServer() s.start_background_task('foo', 'bar', baz='baz') s.eio.start_background_task.assert_called_once_with( 'foo', 'bar', baz='baz' ) - def test_sleep(self, eio): + async def test_sleep(self, eio): eio.return_value.sleep = mock.AsyncMock() s = async_server.AsyncServer() - _run(s.sleep(1.23)) + await s.sleep(1.23) s.eio.sleep.assert_awaited_once_with(1.23) diff --git a/tests/async/test_simple_client.py b/tests/async/test_simple_client.py index a8d08f41..08926922 100644 --- a/tests/async/test_simple_client.py +++ b/tests/async/test_simple_client.py @@ -4,11 +4,10 @@ from socketio import AsyncSimpleClient from socketio.exceptions import SocketIOError, TimeoutError, DisconnectedError -from .helpers import _run class TestAsyncAsyncSimpleClient: - def test_constructor(self): + async def test_constructor(self): client = AsyncSimpleClient(1, '2', a='3', b=4) assert client.client_args == (1, '2') assert client.client_kwargs == {'a': '3', 'b': 4} @@ -16,15 +15,15 @@ def test_constructor(self): assert client.input_buffer == [] assert not client.connected - def test_connect(self): + async def test_connect(self): client = AsyncSimpleClient(123, a='b') with mock.patch('socketio.async_simple_client.AsyncClient') \ as mock_client: mock_client.return_value.connect = mock.AsyncMock() - _run(client.connect('url', headers='h', auth='a', transports='t', - namespace='n', socketio_path='s', - wait_timeout='w')) + await client.connect('url', headers='h', auth='a', transports='t', + namespace='n', socketio_path='s', + wait_timeout='w') mock_client.assert_called_once_with(123, a='b') assert client.client == mock_client() mock_client().connect.assert_awaited_once_with( @@ -35,7 +34,7 @@ def test_connect(self): assert client.namespace == 'n' assert not client.input_event.is_set() - def test_connect_context_manager(self): + async def test_connect_context_manager(self): async def _t(): async with AsyncSimpleClient(123, a='b') as client: with mock.patch('socketio.async_simple_client.AsyncClient') \ @@ -56,17 +55,17 @@ async def _t(): assert client.namespace == 'n' assert not client.input_event.is_set() - _run(_t()) + await _t() - def test_connect_twice(self): + async def test_connect_twice(self): client = AsyncSimpleClient(123, a='b') client.client = mock.MagicMock() client.connected = True with pytest.raises(RuntimeError): - _run(client.connect('url')) + await client.connect('url') - def test_properties(self): + async def test_properties(self): client = AsyncSimpleClient() client.client = mock.MagicMock(transport='websocket') client.client.get_sid.return_value = 'sid' @@ -76,7 +75,7 @@ def test_properties(self): assert client.sid == 'sid' assert client.transport == 'websocket' - def test_emit(self): + async def test_emit(self): client = AsyncSimpleClient() client.client = mock.MagicMock() client.client.emit = mock.AsyncMock() @@ -84,18 +83,18 @@ def test_emit(self): client.connected_event.set() client.connected = True - _run(client.emit('foo', 'bar')) + await client.emit('foo', 'bar') client.client.emit.assert_awaited_once_with('foo', 'bar', namespace='/ns') - def test_emit_disconnected(self): + async def test_emit_disconnected(self): client = AsyncSimpleClient() client.connected_event.set() client.connected = False with pytest.raises(DisconnectedError): - _run(client.emit('foo', 'bar')) + await client.emit('foo', 'bar') - def test_emit_retries(self): + async def test_emit_retries(self): client = AsyncSimpleClient() client.connected_event.set() client.connected = True @@ -103,10 +102,10 @@ def test_emit_retries(self): client.client.emit = mock.AsyncMock() client.client.emit.side_effect = [SocketIOError(), None] - _run(client.emit('foo', 'bar')) + await client.emit('foo', 'bar') client.client.emit.assert_awaited_with('foo', 'bar', namespace='/') - def test_call(self): + async def test_call(self): client = AsyncSimpleClient() client.client = mock.MagicMock() client.client.call = mock.AsyncMock() @@ -115,18 +114,18 @@ def test_call(self): client.connected_event.set() client.connected = True - assert _run(client.call('foo', 'bar')) == 'result' + assert await client.call('foo', 'bar') == 'result' client.client.call.assert_awaited_once_with( 'foo', 'bar', namespace='/ns', timeout=60) - def test_call_disconnected(self): + async def test_call_disconnected(self): client = AsyncSimpleClient() client.connected_event.set() client.connected = False with pytest.raises(DisconnectedError): - _run(client.call('foo', 'bar')) + await client.call('foo', 'bar') - def test_call_retries(self): + async def test_call_retries(self): client = AsyncSimpleClient() client.connected_event.set() client.connected = True @@ -134,17 +133,17 @@ def test_call_retries(self): client.client.call = mock.AsyncMock() client.client.call.side_effect = [SocketIOError(), 'result'] - assert _run(client.call('foo', 'bar')) == 'result' + assert await client.call('foo', 'bar') == 'result' client.client.call.assert_awaited_with('foo', 'bar', namespace='/', timeout=60) - def test_receive_with_input_buffer(self): + async def test_receive_with_input_buffer(self): client = AsyncSimpleClient() client.input_buffer = ['foo', 'bar'] - assert _run(client.receive()) == 'foo' - assert _run(client.receive()) == 'bar' + assert await client.receive() == 'foo' + assert await client.receive() == 'bar' - def test_receive_without_input_buffer(self): + async def test_receive_without_input_buffer(self): client = AsyncSimpleClient() client.connected_event.set() client.connected = True @@ -155,9 +154,9 @@ async def fake_wait(timeout=None): return True client.input_event.wait = fake_wait - assert _run(client.receive()) == 'foo' + assert await client.receive() == 'foo' - def test_receive_with_timeout(self): + async def test_receive_with_timeout(self): client = AsyncSimpleClient() client.connected_event.set() client.connected = True @@ -168,22 +167,22 @@ async def fake_wait(timeout=None): client.input_event.wait = fake_wait with pytest.raises(TimeoutError): - _run(client.receive(timeout=0.01)) + await client.receive(timeout=0.01) - def test_receive_disconnected(self): + async def test_receive_disconnected(self): client = AsyncSimpleClient() client.connected_event.set() client.connected = False with pytest.raises(DisconnectedError): - _run(client.receive()) + await client.receive() - def test_disconnect(self): + async def test_disconnect(self): client = AsyncSimpleClient() mc = mock.MagicMock() mc.disconnect = mock.AsyncMock() client.client = mc client.connected = True - _run(client.disconnect()) - _run(client.disconnect()) + await client.disconnect() + await client.disconnect() mc.disconnect.assert_awaited_once_with() assert client.client is None diff --git a/tox.ini b/tox.ini index 6d809d40..fc0116cb 100644 --- a/tox.ini +++ b/tox.ini @@ -24,6 +24,7 @@ deps= aiohttp msgpack pytest + pytest-asyncio pytest-timeout pytest-cov From bd8555da8523d1a73432685a00eb5acb4d2261f5 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 18 Dec 2024 17:39:03 +0000 Subject: [PATCH 088/173] Pass a `reason` argument to the disconnect handler (#1422) --- docs/client.rst | 23 +++-- docs/server.rst | 23 ++++- examples/client/async/fiddle_client.py | 4 +- examples/client/sync/fiddle_client.py | 4 +- examples/server/aiohttp/app.html | 4 +- examples/server/aiohttp/app.py | 6 +- examples/server/aiohttp/fiddle.py | 6 +- examples/server/asgi/app.html | 4 +- examples/server/asgi/app.py | 4 +- examples/server/asgi/fiddle.py | 4 +- examples/server/javascript/fiddle.js | 4 +- examples/server/sanic/app.html | 4 +- examples/server/sanic/app.py | 4 +- examples/server/sanic/fiddle.py | 4 +- examples/server/tornado/app.py | 4 +- examples/server/tornado/fiddle.py | 4 +- examples/server/tornado/templates/app.html | 4 +- examples/server/wsgi/app.py | 4 +- .../socketio_app/static/index.html | 4 +- .../django_socketio/socketio_app/views.py | 4 +- examples/server/wsgi/fiddle.py | 4 +- examples/server/wsgi/templates/index.html | 4 +- pyproject.toml | 2 +- src/socketio/async_client.py | 33 +++++-- src/socketio/async_namespace.py | 40 ++++++++- src/socketio/async_server.py | 33 +++++-- src/socketio/base_client.py | 5 +- src/socketio/base_server.py | 3 + src/socketio/client.py | 20 +++-- src/socketio/namespace.py | 18 +++- src/socketio/server.py | 24 +++-- tests/async/test_client.py | 89 ++++++++++--------- tests/async/test_manager.py | 2 - tests/async/test_namespace.py | 62 ++++++++++++- tests/async/test_server.py | 53 ++++++++--- tests/common/test_client.py | 72 +++++++-------- tests/common/test_namespace.py | 38 +++++++- tests/common/test_server.py | 38 +++++--- 38 files changed, 469 insertions(+), 193 deletions(-) diff --git a/docs/client.rst b/docs/client.rst index 1a55b71e..e3e1fb2c 100644 --- a/docs/client.rst +++ b/docs/client.rst @@ -312,8 +312,8 @@ server:: print("The connection failed!") @sio.event - def disconnect(): - print("I'm disconnected!") + def disconnect(reason): + print("I'm disconnected! reason:", reason) The ``connect_error`` handler is invoked when a connection attempt fails. If the server provides arguments, these are passed on to the handler. The server @@ -325,7 +325,20 @@ server initiated disconnects, or accidental disconnects, for example due to networking failures. In the case of an accidental disconnection, the client is going to attempt to reconnect immediately after invoking the disconnect handler. As soon as the connection is re-established the connect handler will -be invoked once again. +be invoked once again. The handler receives a ``reason`` argument which +provides the cause of the disconnection:: + + @sio.event + def disconnect(reason): + if reason == sio.reason.CLIENT_DISCONNECT: + print('the client disconnected') + elif reason == sio.reason.SERVER_DISCONNECT: + print('the server disconnected the client') + else: + print('disconnect reason:', reason) + +See the The :attr:`socketio.Client.reason` attribute for a list of possible +disconnection reasons. The ``connect``, ``connect_error`` and ``disconnect`` events have to be defined explicitly and are not invoked on a catch-all event handler. @@ -509,7 +522,7 @@ that belong to a namespace can be created as methods of a subclass of def on_connect(self): pass - def on_disconnect(self): + def on_disconnect(self, reason): pass def on_my_event(self, data): @@ -525,7 +538,7 @@ coroutines if desired:: def on_connect(self): pass - def on_disconnect(self): + def on_disconnect(self, reason): pass async def on_my_event(self, data): diff --git a/docs/server.rst b/docs/server.rst index c20adf9f..ed15ed32 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -232,8 +232,8 @@ automatically when a client connects or disconnects from the server:: print('connect ', sid) @sio.event - def disconnect(sid): - print('disconnect ', sid) + def disconnect(sid, reason): + print('disconnect ', sid, reason) The ``connect`` event is an ideal place to perform user authentication, and any necessary mapping between user entities in the application and the ``sid`` @@ -256,6 +256,21 @@ message:: def connect(sid, environ, auth): raise ConnectionRefusedError('authentication failed') +The disconnect handler receives the ``sid`` assigned to the client and a +``reason``, which provides the cause of the disconnection:: + + @sio.event + def disconnect(sid, reason): + if reason == sio.reason.CLIENT_DISCONNECT: + print('the client disconnected') + elif reason == sio.reason.SERVER_DISCONNECT: + print('the server disconnected the client') + else: + print('disconnect reason:', reason) + +See the The :attr:`socketio.Server.reason` attribute for a list of possible +disconnection reasons. + Catch-All Event Handlers ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -433,7 +448,7 @@ belong to a namespace can be created as methods in a subclass of def on_connect(self, sid, environ): pass - def on_disconnect(self, sid): + def on_disconnect(self, sid, reason): pass def on_my_event(self, sid, data): @@ -449,7 +464,7 @@ if desired:: def on_connect(self, sid, environ): pass - def on_disconnect(self, sid): + def on_disconnect(self, sid, reason): pass async def on_my_event(self, sid, data): diff --git a/examples/client/async/fiddle_client.py b/examples/client/async/fiddle_client.py index 5b43dccd..e5aeb6cc 100644 --- a/examples/client/async/fiddle_client.py +++ b/examples/client/async/fiddle_client.py @@ -10,8 +10,8 @@ async def connect(): @sio.event -async def disconnect(): - print('disconnected from server') +async def disconnect(reason): + print('disconnected from server, reason:', reason) @sio.event diff --git a/examples/client/sync/fiddle_client.py b/examples/client/sync/fiddle_client.py index 50f5e2aa..71a7a540 100644 --- a/examples/client/sync/fiddle_client.py +++ b/examples/client/sync/fiddle_client.py @@ -9,8 +9,8 @@ def connect(): @sio.event -def disconnect(): - print('disconnected from server') +def disconnect(reason): + print('disconnected from server, reason:', reason) @sio.event diff --git a/examples/server/aiohttp/app.html b/examples/server/aiohttp/app.html index 74d404d7..627b9186 100644 --- a/examples/server/aiohttp/app.html +++ b/examples/server/aiohttp/app.html @@ -11,8 +11,8 @@ socket.on('connect', function() { socket.emit('my_event', {data: 'I\'m connected!'}); }); - socket.on('disconnect', function() { - $('#log').append('
Disconnected'); + socket.on('disconnect', function(reason) { + $('#log').append('
Disconnected: ' + reason); }); socket.on('my_response', function(msg) { $('#log').append('
Received: ' + msg.data); diff --git a/examples/server/aiohttp/app.py b/examples/server/aiohttp/app.py index cba51937..1568ca1f 100644 --- a/examples/server/aiohttp/app.py +++ b/examples/server/aiohttp/app.py @@ -70,8 +70,8 @@ async def connect(sid, environ): @sio.event -def disconnect(sid): - print('Client disconnected') +def disconnect(sid, reason): + print('Client disconnected, reason:', reason) app.router.add_static('/static', 'static') @@ -84,4 +84,4 @@ async def init_app(): if __name__ == '__main__': - web.run_app(init_app()) + web.run_app(init_app(), port=5000) diff --git a/examples/server/aiohttp/fiddle.py b/examples/server/aiohttp/fiddle.py index dfde8e10..64ce330d 100644 --- a/examples/server/aiohttp/fiddle.py +++ b/examples/server/aiohttp/fiddle.py @@ -19,8 +19,8 @@ async def connect(sid, environ, auth): @sio.event -def disconnect(sid): - print('disconnected', sid) +def disconnect(sid, reason): + print('disconnected', sid, reason) app.router.add_static('/static', 'static') @@ -28,4 +28,4 @@ def disconnect(sid): if __name__ == '__main__': - web.run_app(app) + web.run_app(app, port=5000) diff --git a/examples/server/asgi/app.html b/examples/server/asgi/app.html index d2f0e9ac..ad826565 100644 --- a/examples/server/asgi/app.html +++ b/examples/server/asgi/app.html @@ -11,8 +11,8 @@ socket.on('connect', function() { socket.emit('my_event', {data: 'I\'m connected!'}); }); - socket.on('disconnect', function() { - $('#log').append('
Disconnected'); + socket.on('disconnect', function(reason) { + $('#log').append('
Disconnected: ' + reason); }); socket.on('my_response', function(msg) { $('#log').append('
Received: ' + msg.data); diff --git a/examples/server/asgi/app.py b/examples/server/asgi/app.py index 36af85f2..d549ab09 100644 --- a/examples/server/asgi/app.py +++ b/examples/server/asgi/app.py @@ -88,8 +88,8 @@ async def test_connect(sid, environ): @sio.on('disconnect') -def test_disconnect(sid): - print('Client disconnected') +def test_disconnect(sid, reason): + print('Client disconnected, reason:', reason) if __name__ == '__main__': diff --git a/examples/server/asgi/fiddle.py b/examples/server/asgi/fiddle.py index 6899ed1a..402a3799 100644 --- a/examples/server/asgi/fiddle.py +++ b/examples/server/asgi/fiddle.py @@ -17,8 +17,8 @@ async def connect(sid, environ, auth): @sio.event -def disconnect(sid): - print('disconnected', sid) +def disconnect(sid, reason): + print('disconnected', sid, reason) if __name__ == '__main__': diff --git a/examples/server/javascript/fiddle.js b/examples/server/javascript/fiddle.js index 940e4da3..c6a039a0 100644 --- a/examples/server/javascript/fiddle.js +++ b/examples/server/javascript/fiddle.js @@ -19,8 +19,8 @@ io.on('connection', socket => { hello: 'you' }); - socket.on('disconnect', () => { - console.log(`disconnect ${socket.id}`); + socket.on('disconnect', (reason) => { + console.log(`disconnect ${socket.id}, reason: ${reason}`); }); }); diff --git a/examples/server/sanic/app.html b/examples/server/sanic/app.html index 30c59643..b87b2df1 100644 --- a/examples/server/sanic/app.html +++ b/examples/server/sanic/app.html @@ -11,8 +11,8 @@ socket.on('connect', function() { socket.emit('my_event', {data: 'I\'m connected!'}); }); - socket.on('disconnect', function() { - $('#log').append('
Disconnected'); + socket.on('disconnect', function(reason) { + $('#log').append('
Disconnected: ' + reason); }); socket.on('my_response', function(msg) { $('#log').append('
Received: ' + msg.data); diff --git a/examples/server/sanic/app.py b/examples/server/sanic/app.py index 7f02d231..447ddff6 100644 --- a/examples/server/sanic/app.py +++ b/examples/server/sanic/app.py @@ -77,8 +77,8 @@ async def connect(sid, environ): @sio.event -def disconnect(sid): - print('Client disconnected') +def disconnect(sid, reason): + print('Client disconnected, reason:', reason) app.static('/static', './static') diff --git a/examples/server/sanic/fiddle.py b/examples/server/sanic/fiddle.py index 5ecb509e..405e6e56 100644 --- a/examples/server/sanic/fiddle.py +++ b/examples/server/sanic/fiddle.py @@ -21,8 +21,8 @@ async def connect(sid, environ, auth): @sio.event -def disconnect(sid): - print('disconnected', sid) +def disconnect(sid, reason): + print('disconnected', sid, reason) app.static('/static', './static') diff --git a/examples/server/tornado/app.py b/examples/server/tornado/app.py index 16f7a191..58317d9b 100644 --- a/examples/server/tornado/app.py +++ b/examples/server/tornado/app.py @@ -75,8 +75,8 @@ async def connect(sid, environ): @sio.event -def disconnect(sid): - print('Client disconnected') +def disconnect(sid, reason): + print('Client disconnected, reason:', reason) def main(): diff --git a/examples/server/tornado/fiddle.py b/examples/server/tornado/fiddle.py index 1e7e9278..b3878a2a 100644 --- a/examples/server/tornado/fiddle.py +++ b/examples/server/tornado/fiddle.py @@ -24,8 +24,8 @@ async def connect(sid, environ, auth): @sio.event -def disconnect(sid): - print('disconnected', sid) +def disconnect(sid, reason): + print('disconnected', sid, reason) def main(): diff --git a/examples/server/tornado/templates/app.html b/examples/server/tornado/templates/app.html index 74d404d7..627b9186 100644 --- a/examples/server/tornado/templates/app.html +++ b/examples/server/tornado/templates/app.html @@ -11,8 +11,8 @@ socket.on('connect', function() { socket.emit('my_event', {data: 'I\'m connected!'}); }); - socket.on('disconnect', function() { - $('#log').append('
Disconnected'); + socket.on('disconnect', function(reason) { + $('#log').append('
Disconnected: ' + reason); }); socket.on('my_response', function(msg) { $('#log').append('
Received: ' + msg.data); diff --git a/examples/server/wsgi/app.py b/examples/server/wsgi/app.py index 7b019fd0..62bd59b1 100644 --- a/examples/server/wsgi/app.py +++ b/examples/server/wsgi/app.py @@ -94,8 +94,8 @@ def connect(sid, environ): @sio.event -def disconnect(sid): - print('Client disconnected') +def disconnect(sid, reason): + print('Client disconnected, reason:', reason) if __name__ == '__main__': diff --git a/examples/server/wsgi/django_socketio/socketio_app/static/index.html b/examples/server/wsgi/django_socketio/socketio_app/static/index.html index 6dbef78b..b10818f4 100644 --- a/examples/server/wsgi/django_socketio/socketio_app/static/index.html +++ b/examples/server/wsgi/django_socketio/socketio_app/static/index.html @@ -11,8 +11,8 @@ socket.on('connect', function() { socket.emit('my_event', {data: 'I\'m connected!'}); }); - socket.on('disconnect', function() { - $('#log').append('
Disconnected'); + socket.on('disconnect', function(reason) { + $('#log').append('
Disconnected: ' + reason); }); socket.on('my_response', function(msg) { $('#log').append('
Received: ' + msg.data); diff --git a/examples/server/wsgi/django_socketio/socketio_app/views.py b/examples/server/wsgi/django_socketio/socketio_app/views.py index 854c0fb0..f54e1d67 100644 --- a/examples/server/wsgi/django_socketio/socketio_app/views.py +++ b/examples/server/wsgi/django_socketio/socketio_app/views.py @@ -78,5 +78,5 @@ def connect(sid, environ): @sio.event -def disconnect(sid): - print('Client disconnected') +def disconnect(sid, reason): + print('Client disconnected, reason:', reason) diff --git a/examples/server/wsgi/fiddle.py b/examples/server/wsgi/fiddle.py index 247751be..e9cd703d 100644 --- a/examples/server/wsgi/fiddle.py +++ b/examples/server/wsgi/fiddle.py @@ -23,8 +23,8 @@ def connect(sid, environ, auth): @sio.event -def disconnect(sid): - print('disconnected', sid) +def disconnect(sid, reason): + print('disconnected', sid, reason) if __name__ == '__main__': diff --git a/examples/server/wsgi/templates/index.html b/examples/server/wsgi/templates/index.html index 8a7308af..e37a6cbd 100644 --- a/examples/server/wsgi/templates/index.html +++ b/examples/server/wsgi/templates/index.html @@ -11,8 +11,8 @@ socket.on('connect', function() { socket.emit('my_event', {data: 'I\'m connected!'}); }); - socket.on('disconnect', function() { - $('#log').append('
Disconnected'); + socket.on('disconnect', function(reason) { + $('#log').append('
Disconnected: ' + reason); }); socket.on('my_response', function(msg) { $('#log').append('
Received: ' + msg.data); diff --git a/pyproject.toml b/pyproject.toml index 8a5453a1..a3ebb6f6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,7 @@ classifiers = [ requires-python = ">=3.8" dependencies = [ "bidict >= 0.21.0", - "python-engineio >= 4.8.0", + "python-engineio >= 4.11.0", ] [project.readme] diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index fb1abc14..463073e7 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -338,7 +338,6 @@ async def shutdown(self): await self.disconnect() elif self._reconnect_task: # pragma: no branch self._reconnect_abort.set() - print(self._reconnect_task) await self._reconnect_task def start_background_task(self, target, *args, **kwargs): @@ -398,8 +397,9 @@ async def _handle_disconnect(self, namespace): if not self.connected: return namespace = namespace or '/' - await self._trigger_event('disconnect', namespace=namespace) - await self._trigger_event('__disconnect_final', namespace=namespace) + await self._trigger_event('disconnect', namespace, + self.reason.SERVER_DISCONNECT) + await self._trigger_event('__disconnect_final', namespace) if namespace in self.namespaces: del self.namespaces[namespace] if not self.namespaces: @@ -462,11 +462,27 @@ async def _trigger_event(self, event, namespace, *args): if handler: if asyncio.iscoroutinefunction(handler): try: - ret = await handler(*args) + try: + ret = await handler(*args) + except TypeError: + # the legacy disconnect event does not take a reason + # argument + if event == 'disconnect': + ret = await handler(*args[:-1]) + else: # pragma: no cover + raise except asyncio.CancelledError: # pragma: no cover ret = None else: - ret = handler(*args) + try: + ret = handler(*args) + except TypeError: + # the legacy disconnect event does not take a reason + # argument + if event == 'disconnect': + ret = handler(*args[:-1]) + else: # pragma: no cover + raise return ret # or else, forward the event to a namepsace handler if one exists @@ -566,16 +582,15 @@ async def _handle_eio_message(self, data): else: raise ValueError('Unknown packet type.') - async def _handle_eio_disconnect(self): + async def _handle_eio_disconnect(self, reason): """Handle the Engine.IO disconnection event.""" self.logger.info('Engine.IO connection dropped') will_reconnect = self.reconnection and self.eio.state == 'connected' if self.connected: for n in self.namespaces: - await self._trigger_event('disconnect', namespace=n) + await self._trigger_event('disconnect', n, reason) if not will_reconnect: - await self._trigger_event('__disconnect_final', - namespace=n) + await self._trigger_event('__disconnect_final', n) self.namespaces = {} self.connected = False self.callbacks = {} diff --git a/src/socketio/async_namespace.py b/src/socketio/async_namespace.py index 89442aeb..42d65089 100644 --- a/src/socketio/async_namespace.py +++ b/src/socketio/async_namespace.py @@ -34,11 +34,27 @@ async def trigger_event(self, event, *args): handler = getattr(self, handler_name) if asyncio.iscoroutinefunction(handler) is True: try: - ret = await handler(*args) + try: + ret = await handler(*args) + except TypeError: + # legacy disconnect events do not have a reason + # argument + if event == 'disconnect': + ret = await handler(*args[:-1]) + else: # pragma: no cover + raise except asyncio.CancelledError: # pragma: no cover ret = None else: - ret = handler(*args) + try: + ret = handler(*args) + except TypeError: + # legacy disconnect events do not have a reason + # argument + if event == 'disconnect': + ret = handler(*args[:-1]) + else: # pragma: no cover + raise return ret async def emit(self, event, data=None, to=None, room=None, skip_sid=None, @@ -199,11 +215,27 @@ async def trigger_event(self, event, *args): handler = getattr(self, handler_name) if asyncio.iscoroutinefunction(handler) is True: try: - ret = await handler(*args) + try: + ret = await handler(*args) + except TypeError: + # legacy disconnect events do not have a reason + # argument + if event == 'disconnect': + ret = await handler(*args[:-1]) + else: # pragma: no cover + raise except asyncio.CancelledError: # pragma: no cover ret = None else: - ret = handler(*args) + try: + ret = handler(*args) + except TypeError: + # legacy disconnect events do not have a reason + # argument + if event == 'disconnect': + ret = handler(*args[:-1]) + else: # pragma: no cover + raise return ret async def emit(self, event, data=None, namespace=None, callback=None): diff --git a/src/socketio/async_server.py b/src/socketio/async_server.py index 9b0e9774..f10fb8ae 100644 --- a/src/socketio/async_server.py +++ b/src/socketio/async_server.py @@ -427,7 +427,8 @@ async def disconnect(self, sid, namespace=None, ignore_queue=False): eio_sid = self.manager.pre_disconnect(sid, namespace=namespace) await self._send_packet(eio_sid, self.packet_class( packet.DISCONNECT, namespace=namespace)) - await self._trigger_event('disconnect', namespace, sid) + await self._trigger_event('disconnect', namespace, sid, + self.reason.SERVER_DISCONNECT) await self.manager.disconnect(sid, namespace=namespace, ignore_queue=True) @@ -575,14 +576,15 @@ async def _handle_connect(self, eio_sid, namespace, data): await self._send_packet(eio_sid, self.packet_class( packet.CONNECT, {'sid': sid}, namespace=namespace)) - async def _handle_disconnect(self, eio_sid, namespace): + async def _handle_disconnect(self, eio_sid, namespace, reason=None): """Handle a client disconnect.""" namespace = namespace or '/' sid = self.manager.sid_from_eio_sid(eio_sid, namespace) if not self.manager.is_connected(sid, namespace): # pragma: no cover return self.manager.pre_disconnect(sid, namespace=namespace) - await self._trigger_event('disconnect', namespace, sid) + await self._trigger_event('disconnect', namespace, sid, + reason or self.reason.CLIENT_DISCONNECT) await self.manager.disconnect(sid, namespace, ignore_queue=True) async def _handle_event(self, eio_sid, namespace, id, data): @@ -634,11 +636,25 @@ async def _trigger_event(self, event, namespace, *args): if handler: if asyncio.iscoroutinefunction(handler): try: - ret = await handler(*args) + try: + ret = await handler(*args) + except TypeError: + # legacy disconnect events use only one argument + if event == 'disconnect': + ret = await handler(*args[:-1]) + else: # pragma: no cover + raise except asyncio.CancelledError: # pragma: no cover ret = None else: - ret = handler(*args) + try: + ret = handler(*args) + except TypeError: + # legacy disconnect events use only one argument + if event == 'disconnect': + ret = handler(*args[:-1]) + else: # pragma: no cover + raise return ret # or else, forward the event to a namespace handler if one exists handler, args = self._get_namespace_handler(namespace, args) @@ -671,7 +687,8 @@ async def _handle_eio_message(self, eio_sid, data): if pkt.packet_type == packet.CONNECT: await self._handle_connect(eio_sid, pkt.namespace, pkt.data) elif pkt.packet_type == packet.DISCONNECT: - await self._handle_disconnect(eio_sid, pkt.namespace) + await self._handle_disconnect(eio_sid, pkt.namespace, + self.reason.CLIENT_DISCONNECT) elif pkt.packet_type == packet.EVENT: await self._handle_event(eio_sid, pkt.namespace, pkt.id, pkt.data) @@ -686,10 +703,10 @@ async def _handle_eio_message(self, eio_sid, data): else: raise ValueError('Unknown packet type.') - async def _handle_eio_disconnect(self, eio_sid): + async def _handle_eio_disconnect(self, eio_sid, reason): """Handle Engine.IO disconnect event.""" for n in list(self.manager.get_namespaces()).copy(): - await self._handle_disconnect(eio_sid, n) + await self._handle_disconnect(eio_sid, n, reason) if eio_sid in self.environ: del self.environ[eio_sid] diff --git a/src/socketio/base_client.py b/src/socketio/base_client.py index 1becf914..7bf44207 100644 --- a/src/socketio/base_client.py +++ b/src/socketio/base_client.py @@ -3,6 +3,8 @@ import signal import threading +import engineio + from . import base_namespace from . import packet @@ -31,6 +33,7 @@ def signal_handler(sig, frame): # pragma: no cover class BaseClient: reserved_events = ['connect', 'connect_error', 'disconnect', '__disconnect_final'] + reason = engineio.Client.reason def __init__(self, reconnection=True, reconnection_attempts=0, reconnection_delay=1, reconnection_delay_max=5, @@ -285,7 +288,7 @@ def _handle_eio_connect(self): # pragma: no cover def _handle_eio_message(self, data): # pragma: no cover raise NotImplementedError() - def _handle_eio_disconnect(self): # pragma: no cover + def _handle_eio_disconnect(self, reason): # pragma: no cover raise NotImplementedError() def _engineio_client_class(self): # pragma: no cover diff --git a/src/socketio/base_server.py b/src/socketio/base_server.py index d5a353bc..d134eba1 100644 --- a/src/socketio/base_server.py +++ b/src/socketio/base_server.py @@ -1,5 +1,7 @@ import logging +import engineio + from . import manager from . import base_namespace from . import packet @@ -9,6 +11,7 @@ class BaseServer: reserved_events = ['connect', 'disconnect'] + reason = engineio.Server.reason def __init__(self, client_manager=None, logger=False, serializer='default', json=None, async_handlers=True, always_connect=False, diff --git a/src/socketio/client.py b/src/socketio/client.py index c4f9eaa7..ade2dd6f 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -377,8 +377,9 @@ def _handle_disconnect(self, namespace): if not self.connected: return namespace = namespace or '/' - self._trigger_event('disconnect', namespace=namespace) - self._trigger_event('__disconnect_final', namespace=namespace) + self._trigger_event('disconnect', namespace, + self.reason.SERVER_DISCONNECT) + self._trigger_event('__disconnect_final', namespace) if namespace in self.namespaces: del self.namespaces[namespace] if not self.namespaces: @@ -436,7 +437,14 @@ def _trigger_event(self, event, namespace, *args): # first see if we have an explicit handler for the event handler, args = self._get_event_handler(event, namespace, args) if handler: - return handler(*args) + try: + return handler(*args) + except TypeError: + # the legacy disconnect event does not take a reason argument + if event == 'disconnect': + return handler(*args[:-1]) + else: # pragma: no cover + raise # or else, forward the event to a namespace handler if one exists handler, args = self._get_namespace_handler(namespace, args) @@ -525,15 +533,15 @@ def _handle_eio_message(self, data): else: raise ValueError('Unknown packet type.') - def _handle_eio_disconnect(self): + def _handle_eio_disconnect(self, reason): """Handle the Engine.IO disconnection event.""" self.logger.info('Engine.IO connection dropped') will_reconnect = self.reconnection and self.eio.state == 'connected' if self.connected: for n in self.namespaces: - self._trigger_event('disconnect', namespace=n) + self._trigger_event('disconnect', n, reason) if not will_reconnect: - self._trigger_event('__disconnect_final', namespace=n) + self._trigger_event('__disconnect_final', n) self.namespaces = {} self.connected = False self.callbacks = {} diff --git a/src/socketio/namespace.py b/src/socketio/namespace.py index 3bf4f95b..60cab783 100644 --- a/src/socketio/namespace.py +++ b/src/socketio/namespace.py @@ -23,7 +23,14 @@ def trigger_event(self, event, *args): """ handler_name = 'on_' + (event or '') if hasattr(self, handler_name): - return getattr(self, handler_name)(*args) + try: + return getattr(self, handler_name)(*args) + except TypeError: + # legacy disconnect events do not have a reason argument + if event == 'disconnect': + return getattr(self, handler_name)(*args[:-1]) + else: # pragma: no cover + raise def emit(self, event, data=None, to=None, room=None, skip_sid=None, namespace=None, callback=None, ignore_queue=False): @@ -154,7 +161,14 @@ def trigger_event(self, event, *args): """ handler_name = 'on_' + (event or '') if hasattr(self, handler_name): - return getattr(self, handler_name)(*args) + try: + return getattr(self, handler_name)(*args) + except TypeError: + # legacy disconnect events do not have a reason argument + if event == 'disconnect': + return getattr(self, handler_name)(*args[:-1]) + else: # pragma: no cover + raise def emit(self, event, data=None, namespace=None, callback=None): """Emit a custom event to the server. diff --git a/src/socketio/server.py b/src/socketio/server.py index ae73df6a..71c702de 100644 --- a/src/socketio/server.py +++ b/src/socketio/server.py @@ -403,7 +403,8 @@ def disconnect(self, sid, namespace=None, ignore_queue=False): eio_sid = self.manager.pre_disconnect(sid, namespace=namespace) self._send_packet(eio_sid, self.packet_class( packet.DISCONNECT, namespace=namespace)) - self._trigger_event('disconnect', namespace, sid) + self._trigger_event('disconnect', namespace, sid, + self.reason.SERVER_DISCONNECT) self.manager.disconnect(sid, namespace=namespace, ignore_queue=True) @@ -557,14 +558,15 @@ def _handle_connect(self, eio_sid, namespace, data): self._send_packet(eio_sid, self.packet_class( packet.CONNECT, {'sid': sid}, namespace=namespace)) - def _handle_disconnect(self, eio_sid, namespace): + def _handle_disconnect(self, eio_sid, namespace, reason=None): """Handle a client disconnect.""" namespace = namespace or '/' sid = self.manager.sid_from_eio_sid(eio_sid, namespace) if not self.manager.is_connected(sid, namespace): # pragma: no cover return self.manager.pre_disconnect(sid, namespace=namespace) - self._trigger_event('disconnect', namespace, sid) + self._trigger_event('disconnect', namespace, sid, + reason or self.reason.CLIENT_DISCONNECT) self.manager.disconnect(sid, namespace, ignore_queue=True) def _handle_event(self, eio_sid, namespace, id, data): @@ -611,7 +613,14 @@ def _trigger_event(self, event, namespace, *args): # first see if we have an explicit handler for the event handler, args = self._get_event_handler(event, namespace, args) if handler: - return handler(*args) + try: + return handler(*args) + except TypeError: + # legacy disconnect events use only one argument + if event == 'disconnect': + return handler(*args[:-1]) + else: # pragma: no cover + raise # or else, forward the event to a namespace handler if one exists handler, args = self._get_namespace_handler(namespace, args) if handler: @@ -642,7 +651,8 @@ def _handle_eio_message(self, eio_sid, data): if pkt.packet_type == packet.CONNECT: self._handle_connect(eio_sid, pkt.namespace, pkt.data) elif pkt.packet_type == packet.DISCONNECT: - self._handle_disconnect(eio_sid, pkt.namespace) + self._handle_disconnect(eio_sid, pkt.namespace, + self.reason.CLIENT_DISCONNECT) elif pkt.packet_type == packet.EVENT: self._handle_event(eio_sid, pkt.namespace, pkt.id, pkt.data) elif pkt.packet_type == packet.ACK: @@ -655,10 +665,10 @@ def _handle_eio_message(self, eio_sid, data): else: raise ValueError('Unknown packet type.') - def _handle_eio_disconnect(self, eio_sid): + def _handle_eio_disconnect(self, eio_sid, reason): """Handle Engine.IO disconnect event.""" for n in list(self.manager.get_namespaces()).copy(): - self._handle_disconnect(eio_sid, n) + self._handle_disconnect(eio_sid, n, reason) if eio_sid in self.environ: del self.environ[eio_sid] diff --git a/tests/async/test_client.py b/tests/async/test_client.py index 26681b76..3eec1a88 100644 --- a/tests/async/test_client.py +++ b/tests/async/test_client.py @@ -578,11 +578,9 @@ async def test_handle_disconnect(self): c._trigger_event = mock.AsyncMock() await c._handle_disconnect('/') c._trigger_event.assert_any_await( - 'disconnect', namespace='/' - ) - c._trigger_event.assert_any_await( - '__disconnect_final', namespace='/' + 'disconnect', '/', c.reason.SERVER_DISCONNECT ) + c._trigger_event.assert_any_await('__disconnect_final', '/') assert not c.connected await c._handle_disconnect('/') assert c._trigger_event.await_count == 2 @@ -593,21 +591,15 @@ async def test_handle_disconnect_namespace(self): c.namespaces = {'/foo': '1', '/bar': '2'} c._trigger_event = mock.AsyncMock() await c._handle_disconnect('/foo') - c._trigger_event.assert_any_await( - 'disconnect', namespace='/foo' - ) - c._trigger_event.assert_any_await( - '__disconnect_final', namespace='/foo' - ) + c._trigger_event.assert_any_await('disconnect', '/foo', + c.reason.SERVER_DISCONNECT) + c._trigger_event.assert_any_await('__disconnect_final', '/foo') assert c.namespaces == {'/bar': '2'} assert c.connected await c._handle_disconnect('/bar') - c._trigger_event.assert_any_await( - 'disconnect', namespace='/bar' - ) - c._trigger_event.assert_any_await( - '__disconnect_final', namespace='/bar' - ) + c._trigger_event.assert_any_await('disconnect', '/bar', + c.reason.SERVER_DISCONNECT) + c._trigger_event.assert_any_await('__disconnect_final', '/bar') assert c.namespaces == {} assert not c.connected @@ -617,12 +609,9 @@ async def test_handle_disconnect_unknown_namespace(self): c.namespaces = {'/foo': '1', '/bar': '2'} c._trigger_event = mock.AsyncMock() await c._handle_disconnect('/baz') - c._trigger_event.assert_any_await( - 'disconnect', namespace='/baz' - ) - c._trigger_event.assert_any_await( - '__disconnect_final', namespace='/baz' - ) + c._trigger_event.assert_any_await('disconnect', '/baz', + c.reason.SERVER_DISCONNECT) + c._trigger_event.assert_any_await('__disconnect_final', '/baz') assert c.namespaces == {'/foo': '1', '/bar': '2'} assert c.connected @@ -632,8 +621,9 @@ async def test_handle_disconnect_default_namespaces(self): c.namespaces = {'/foo': '1', '/bar': '2'} c._trigger_event = mock.AsyncMock() await c._handle_disconnect('/') - c._trigger_event.assert_any_await('disconnect', namespace='/') - c._trigger_event.assert_any_await('__disconnect_final', namespace='/') + c._trigger_event.assert_any_await('disconnect', '/', + c.reason.SERVER_DISCONNECT) + c._trigger_event.assert_any_await('__disconnect_final', '/') assert c.namespaces == {'/foo': '1', '/bar': '2'} assert c.connected @@ -818,6 +808,26 @@ async def test_trigger_event_namespace(self): handler.assert_awaited_once_with(1, '2') catchall_handler.assert_awaited_once_with('bar', 1, '2', 3) + async def test_trigger_legacy_disconnect_event(self): + c = async_client.AsyncClient() + + @c.on('disconnect') + def baz(): + return 'baz' + + r = await c._trigger_event('disconnect', '/', 'foo') + assert r == 'baz' + + async def test_trigger_legacy_disconnect_event_async(self): + c = async_client.AsyncClient() + + @c.on('disconnect') + async def baz(): + return 'baz' + + r = await c._trigger_event('disconnect', '/', 'foo') + assert r == 'baz' + async def test_trigger_event_class_namespace(self): c = async_client.AsyncClient() result = [] @@ -1127,10 +1137,8 @@ async def test_eio_disconnect(self): c.start_background_task = mock.MagicMock() c.sid = 'foo' c.eio.state = 'connected' - await c._handle_eio_disconnect() - c._trigger_event.assert_awaited_once_with( - 'disconnect', namespace='/' - ) + await c._handle_eio_disconnect('foo') + c._trigger_event.assert_awaited_once_with('disconnect', '/', 'foo') assert c.sid is None assert not c.connected @@ -1141,9 +1149,13 @@ async def test_eio_disconnect_namespaces(self): c._trigger_event = mock.AsyncMock() c.sid = 'foo' c.eio.state = 'connected' - await c._handle_eio_disconnect() - c._trigger_event.assert_any_await('disconnect', namespace='/foo') - c._trigger_event.assert_any_await('disconnect', namespace='/bar') + await c._handle_eio_disconnect(c.reason.CLIENT_DISCONNECT) + c._trigger_event.assert_any_await('disconnect', '/foo', + c.reason.CLIENT_DISCONNECT) + c._trigger_event.assert_any_await('disconnect', '/bar', + c.reason.CLIENT_DISCONNECT) + c._trigger_event.asserT_any_await('disconnect', '/', + c.reason.CLIENT_DISCONNECT) assert c.sid is None assert not c.connected @@ -1151,14 +1163,14 @@ async def test_eio_disconnect_reconnect(self): c = async_client.AsyncClient(reconnection=True) c.start_background_task = mock.MagicMock() c.eio.state = 'connected' - await c._handle_eio_disconnect() + await c._handle_eio_disconnect(c.reason.CLIENT_DISCONNECT) c.start_background_task.assert_called_once_with(c._handle_reconnect) async def test_eio_disconnect_self_disconnect(self): c = async_client.AsyncClient(reconnection=True) c.start_background_task = mock.MagicMock() c.eio.state = 'disconnected' - await c._handle_eio_disconnect() + await c._handle_eio_disconnect(c.reason.CLIENT_DISCONNECT) c.start_background_task.assert_not_called() async def test_eio_disconnect_no_reconnect(self): @@ -1169,13 +1181,10 @@ async def test_eio_disconnect_no_reconnect(self): c.start_background_task = mock.MagicMock() c.sid = 'foo' c.eio.state = 'connected' - await c._handle_eio_disconnect() - c._trigger_event.assert_any_await( - 'disconnect', namespace='/' - ) - c._trigger_event.assert_any_await( - '__disconnect_final', namespace='/' - ) + await c._handle_eio_disconnect(c.reason.TRANSPORT_ERROR) + c._trigger_event.assert_any_await('disconnect', '/', + c.reason.TRANSPORT_ERROR) + c._trigger_event.assert_any_await('__disconnect_final', '/') assert c.sid is None assert not c.connected c.start_background_task.assert_not_called() diff --git a/tests/async/test_manager.py b/tests/async/test_manager.py index fd5fe816..aa890649 100644 --- a/tests/async/test_manager.py +++ b/tests/async/test_manager.py @@ -183,8 +183,6 @@ async def test_close_room(self): await self.bm.enter_room(sid, '/foo', 'bar') await self.bm.enter_room(sid, '/foo', 'bar') await self.bm.close_room('bar', '/foo') - from pprint import pprint - pprint(self.bm.rooms) assert 'bar' not in self.bm.rooms['/foo'] async def test_close_invalid_room(self): diff --git a/tests/async/test_namespace.py b/tests/async/test_namespace.py index ad9b1a03..526d6769 100644 --- a/tests/async/test_namespace.py +++ b/tests/async/test_namespace.py @@ -19,13 +19,37 @@ async def on_connect(self, sid, environ): async def test_disconnect_event(self): result = {} + class MyNamespace(async_namespace.AsyncNamespace): + async def on_disconnect(self, sid, reason): + result['result'] = (sid, reason) + + ns = MyNamespace('/foo') + ns._set_server(mock.MagicMock()) + await ns.trigger_event('disconnect', 'sid', 'foo') + assert result['result'] == ('sid', 'foo') + + async def test_legacy_disconnect_event(self): + result = {} + + class MyNamespace(async_namespace.AsyncNamespace): + def on_disconnect(self, sid): + result['result'] = sid + + ns = MyNamespace('/foo') + ns._set_server(mock.MagicMock()) + await ns.trigger_event('disconnect', 'sid', 'foo') + assert result['result'] == 'sid' + + async def test_legacy_disconnect_event_async(self): + result = {} + class MyNamespace(async_namespace.AsyncNamespace): async def on_disconnect(self, sid): result['result'] = sid ns = MyNamespace('/foo') ns._set_server(mock.MagicMock()) - await ns.trigger_event('disconnect', 'sid') + await ns.trigger_event('disconnect', 'sid', 'foo') assert result['result'] == 'sid' async def test_sync_event(self): @@ -242,6 +266,42 @@ async def test_disconnect(self): await ns.disconnect('sid', namespace='/bar') ns.server.disconnect.assert_awaited_with('sid', namespace='/bar') + async def test_disconnect_event_client(self): + result = {} + + class MyNamespace(async_namespace.AsyncClientNamespace): + async def on_disconnect(self, reason): + result['result'] = reason + + ns = MyNamespace('/foo') + ns._set_client(mock.MagicMock()) + await ns.trigger_event('disconnect', 'foo') + assert result['result'] == 'foo' + + async def test_legacy_disconnect_event_client(self): + result = {} + + class MyNamespace(async_namespace.AsyncClientNamespace): + def on_disconnect(self): + result['result'] = 'ok' + + ns = MyNamespace('/foo') + ns._set_client(mock.MagicMock()) + await ns.trigger_event('disconnect', 'foo') + assert result['result'] == 'ok' + + async def test_legacy_disconnect_event_client_async(self): + result = {} + + class MyNamespace(async_namespace.AsyncClientNamespace): + async def on_disconnect(self): + result['result'] = 'ok' + + ns = MyNamespace('/foo') + ns._set_client(mock.MagicMock()) + await ns.trigger_event('disconnect', 'foo') + assert result['result'] == 'ok' + async def test_sync_event_client(self): result = {} diff --git a/tests/async/test_server.py b/tests/async/test_server.py index d9129d46..f60de27a 100644 --- a/tests/async/test_server.py +++ b/tests/async/test_server.py @@ -56,7 +56,7 @@ async def test_on_event(self, eio): def foo(): pass - def bar(): + def bar(reason): pass s.on('disconnect', bar) @@ -537,8 +537,36 @@ async def test_handle_disconnect(self, eio): s.on('disconnect', handler) await s._handle_eio_connect('123', 'environ') await s._handle_eio_message('123', '0') - await s._handle_eio_disconnect('123') - handler.assert_called_once_with('1') + await s._handle_eio_disconnect('123', 'foo') + handler.assert_called_once_with('1', 'foo') + s.manager.disconnect.assert_awaited_once_with( + '1', '/', ignore_queue=True) + assert s.environ == {} + + async def test_handle_legacy_disconnect(self, eio): + eio.return_value.send = mock.AsyncMock() + s = async_server.AsyncServer() + s.manager.disconnect = mock.AsyncMock() + handler = mock.MagicMock(side_effect=[TypeError, None]) + s.on('disconnect', handler) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') + await s._handle_eio_disconnect('123', 'foo') + handler.assert_called_with('1') + s.manager.disconnect.assert_awaited_once_with( + '1', '/', ignore_queue=True) + assert s.environ == {} + + async def test_handle_legacy_disconnect_async(self, eio): + eio.return_value.send = mock.AsyncMock() + s = async_server.AsyncServer() + s.manager.disconnect = mock.AsyncMock() + handler = mock.AsyncMock(side_effect=[TypeError, None]) + s.on('disconnect', handler) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') + await s._handle_eio_disconnect('123', 'foo') + handler.assert_awaited_with('1') s.manager.disconnect.assert_awaited_once_with( '1', '/', ignore_queue=True) assert s.environ == {} @@ -552,9 +580,9 @@ async def test_handle_disconnect_namespace(self, eio): s.on('disconnect', handler_namespace, namespace='/foo') await s._handle_eio_connect('123', 'environ') await s._handle_eio_message('123', '0/foo,') - await s._handle_eio_disconnect('123') + await s._handle_eio_disconnect('123', 'foo') handler.assert_not_called() - handler_namespace.assert_called_once_with('1') + handler_namespace.assert_called_once_with('1', 'foo') assert s.environ == {} async def test_handle_disconnect_only_namespace(self, eio): @@ -568,13 +596,14 @@ async def test_handle_disconnect_only_namespace(self, eio): await s._handle_eio_message('123', '0/foo,') await s._handle_eio_message('123', '1/foo,') assert handler.call_count == 0 - handler_namespace.assert_called_once_with('1') + handler_namespace.assert_called_once_with( + '1', s.reason.CLIENT_DISCONNECT) assert s.environ == {'123': 'environ'} async def test_handle_disconnect_unknown_client(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - await s._handle_eio_disconnect('123') + await s._handle_eio_disconnect('123', 'foo') async def test_handle_event(self, eio): eio.return_value.send = mock.AsyncMock() @@ -624,7 +653,8 @@ async def test_handle_event_with_catchall_namespace(self, eio): await s._handle_eio_message('123', '2/bar,["msg","a","b"]') await s._handle_eio_message('123', '2/foo,["my message","a","b","c"]') await s._handle_eio_message('123', '2/bar,["my message","a","b","c"]') - await s._trigger_event('disconnect', '/bar', sid_bar) + await s._trigger_event('disconnect', '/bar', sid_bar, + s.reason.CLIENT_DISCONNECT) connect_star_handler.assert_called_once_with('/bar', sid_bar) msg_foo_handler.assert_called_once_with(sid_foo, 'a', 'b') msg_star_handler.assert_called_once_with('/bar', sid_bar, 'a', 'b') @@ -884,8 +914,8 @@ class MyNamespace(async_namespace.AsyncNamespace): def on_connect(self, sid, environ): result['result'] = (sid, environ) - async def on_disconnect(self, sid): - result['result'] = ('disconnect', sid) + async def on_disconnect(self, sid, reason): + result['result'] = ('disconnect', sid, reason) async def on_foo(self, sid, data): result['result'] = (sid, data) @@ -908,7 +938,8 @@ async def on_baz(self, sid, data1, data2): await s._handle_eio_message('123', '2/foo,["baz","a","b"]') assert result['result'] == ('a', 'b') await s.disconnect('1', '/foo') - assert result['result'] == ('disconnect', '1') + assert result['result'] == ('disconnect', '1', + s.reason.SERVER_DISCONNECT) async def test_catchall_namespace_handler(self, eio): eio.return_value.send = mock.AsyncMock() diff --git a/tests/common/test_client.py b/tests/common/test_client.py index c7399dc1..ac930c79 100644 --- a/tests/common/test_client.py +++ b/tests/common/test_client.py @@ -752,8 +752,9 @@ def test_handle_disconnect(self): c.connected = True c._trigger_event = mock.MagicMock() c._handle_disconnect('/') - c._trigger_event.assert_any_call('disconnect', namespace='/') - c._trigger_event.assert_any_call('__disconnect_final', namespace='/') + c._trigger_event.assert_any_call('disconnect', '/', + c.reason.SERVER_DISCONNECT) + c._trigger_event.assert_any_call('__disconnect_final', '/') assert not c.connected c._handle_disconnect('/') assert c._trigger_event.call_count == 2 @@ -764,21 +765,15 @@ def test_handle_disconnect_namespace(self): c.namespaces = {'/foo': '1', '/bar': '2'} c._trigger_event = mock.MagicMock() c._handle_disconnect('/foo') - c._trigger_event.assert_any_call( - 'disconnect', namespace='/foo' - ) - c._trigger_event.assert_any_call( - '__disconnect_final', namespace='/foo' - ) + c._trigger_event.assert_any_call('disconnect', '/foo', + c.reason.SERVER_DISCONNECT) + c._trigger_event.assert_any_call('__disconnect_final', '/foo') assert c.namespaces == {'/bar': '2'} assert c.connected c._handle_disconnect('/bar') - c._trigger_event.assert_any_call( - 'disconnect', namespace='/bar' - ) - c._trigger_event.assert_any_call( - '__disconnect_final', namespace='/bar' - ) + c._trigger_event.assert_any_call('disconnect', '/bar', + c.reason.SERVER_DISCONNECT) + c._trigger_event.assert_any_call('__disconnect_final', '/bar') assert c.namespaces == {} assert not c.connected @@ -788,12 +783,9 @@ def test_handle_disconnect_unknown_namespace(self): c.namespaces = {'/foo': '1', '/bar': '2'} c._trigger_event = mock.MagicMock() c._handle_disconnect('/baz') - c._trigger_event.assert_any_call( - 'disconnect', namespace='/baz' - ) - c._trigger_event.assert_any_call( - '__disconnect_final', namespace='/baz' - ) + c._trigger_event.assert_any_call('disconnect', '/baz', + c.reason.SERVER_DISCONNECT) + c._trigger_event.assert_any_call('__disconnect_final', '/baz') assert c.namespaces == {'/foo': '1', '/bar': '2'} assert c.connected @@ -803,9 +795,9 @@ def test_handle_disconnect_default_namespace(self): c.namespaces = {'/foo': '1', '/bar': '2'} c._trigger_event = mock.MagicMock() c._handle_disconnect('/') - print(c._trigger_event.call_args_list) - c._trigger_event.assert_any_call('disconnect', namespace='/') - c._trigger_event.assert_any_call('__disconnect_final', namespace='/') + c._trigger_event.assert_any_call('disconnect', '/', + c.reason.SERVER_DISCONNECT) + c._trigger_event.assert_any_call('__disconnect_final', '/') assert c.namespaces == {'/foo': '1', '/bar': '2'} assert c.connected @@ -1003,8 +995,8 @@ class MyNamespace(namespace.ClientNamespace): def on_connect(self, ns): result['result'] = (ns,) - def on_disconnect(self, ns): - result['result'] = ('disconnect', ns) + def on_disconnect(self, ns, reason): + result['result'] = ('disconnect', ns, reason) def on_foo(self, ns, data): result['result'] = (ns, data) @@ -1025,8 +1017,8 @@ def on_baz(self, ns, data1, data2): assert result['result'] == 'bar/foo' c._trigger_event('baz', '/foo', 'a', 'b') assert result['result'] == ('/foo', 'a', 'b') - c._trigger_event('disconnect', '/foo') - assert result['result'] == ('disconnect', '/foo') + c._trigger_event('disconnect', '/foo', 'bar') + assert result['result'] == ('disconnect', '/foo', 'bar') def test_trigger_event_class_namespace(self): c = client.Client() @@ -1286,8 +1278,8 @@ def test_eio_disconnect(self): c.start_background_task = mock.MagicMock() c.sid = 'foo' c.eio.state = 'connected' - c._handle_eio_disconnect() - c._trigger_event.assert_called_once_with('disconnect', namespace='/') + c._handle_eio_disconnect('foo') + c._trigger_event.assert_called_once_with('disconnect', '/', 'foo') assert c.sid is None assert not c.connected @@ -1299,10 +1291,13 @@ def test_eio_disconnect_namespaces(self): c.start_background_task = mock.MagicMock() c.sid = 'foo' c.eio.state = 'connected' - c._handle_eio_disconnect() - c._trigger_event.assert_any_call('disconnect', namespace='/foo') - c._trigger_event.assert_any_call('disconnect', namespace='/bar') - c._trigger_event.assert_any_call('disconnect', namespace='/') + c._handle_eio_disconnect(c.reason.CLIENT_DISCONNECT) + c._trigger_event.assert_any_call('disconnect', '/foo', + c.reason.CLIENT_DISCONNECT) + c._trigger_event.assert_any_call('disconnect', '/bar', + c.reason.CLIENT_DISCONNECT) + c._trigger_event.assert_any_call('disconnect', '/', + c.reason.CLIENT_DISCONNECT) assert c.sid is None assert not c.connected @@ -1310,14 +1305,14 @@ def test_eio_disconnect_reconnect(self): c = client.Client(reconnection=True) c.start_background_task = mock.MagicMock() c.eio.state = 'connected' - c._handle_eio_disconnect() + c._handle_eio_disconnect(c.reason.CLIENT_DISCONNECT) c.start_background_task.assert_called_once_with(c._handle_reconnect) def test_eio_disconnect_self_disconnect(self): c = client.Client(reconnection=True) c.start_background_task = mock.MagicMock() c.eio.state = 'disconnected' - c._handle_eio_disconnect() + c._handle_eio_disconnect(c.reason.CLIENT_DISCONNECT) c.start_background_task.assert_not_called() def test_eio_disconnect_no_reconnect(self): @@ -1328,9 +1323,10 @@ def test_eio_disconnect_no_reconnect(self): c.start_background_task = mock.MagicMock() c.sid = 'foo' c.eio.state = 'connected' - c._handle_eio_disconnect() - c._trigger_event.assert_any_call('disconnect', namespace='/') - c._trigger_event.assert_any_call('__disconnect_final', namespace='/') + c._handle_eio_disconnect(c.reason.TRANSPORT_ERROR) + c._trigger_event.assert_any_call('disconnect', '/', + c.reason.TRANSPORT_ERROR) + c._trigger_event.assert_any_call('__disconnect_final', '/') assert c.sid is None assert not c.connected c.start_background_task.assert_not_called() diff --git a/tests/common/test_namespace.py b/tests/common/test_namespace.py index 8bfa9899..f1476e41 100644 --- a/tests/common/test_namespace.py +++ b/tests/common/test_namespace.py @@ -19,13 +19,25 @@ def on_connect(self, sid, environ): def test_disconnect_event(self): result = {} + class MyNamespace(namespace.Namespace): + def on_disconnect(self, sid, reason): + result['result'] = (sid, reason) + + ns = MyNamespace('/foo') + ns._set_server(mock.MagicMock()) + ns.trigger_event('disconnect', 'sid', 'foo') + assert result['result'] == ('sid', 'foo') + + def test_legacy_disconnect_event(self): + result = {} + class MyNamespace(namespace.Namespace): def on_disconnect(self, sid): result['result'] = sid ns = MyNamespace('/foo') ns._set_server(mock.MagicMock()) - ns.trigger_event('disconnect', 'sid') + ns.trigger_event('disconnect', 'sid', 'foo') assert result['result'] == 'sid' def test_event(self): @@ -216,6 +228,30 @@ def test_disconnect(self): ns.disconnect('sid', namespace='/bar') ns.server.disconnect.assert_called_with('sid', namespace='/bar') + def test_disconnect_event_client(self): + result = {} + + class MyNamespace(namespace.ClientNamespace): + def on_disconnect(self, reason): + result['result'] = reason + + ns = MyNamespace('/foo') + ns._set_client(mock.MagicMock()) + ns.trigger_event('disconnect', 'foo') + assert result['result'] == 'foo' + + def test_legacy_disconnect_event_client(self): + result = {} + + class MyNamespace(namespace.ClientNamespace): + def on_disconnect(self): + result['result'] = 'ok' + + ns = MyNamespace('/foo') + ns._set_client(mock.MagicMock()) + ns.trigger_event('disconnect', 'foo') + assert result['result'] == 'ok' + def test_event_not_found_client(self): result = {} diff --git a/tests/common/test_server.py b/tests/common/test_server.py index 57ddc2f4..445d5d9e 100644 --- a/tests/common/test_server.py +++ b/tests/common/test_server.py @@ -39,7 +39,7 @@ def test_on_event(self, eio): def foo(): pass - def bar(): + def bar(reason): pass s.on('disconnect', bar) @@ -510,8 +510,21 @@ def test_handle_disconnect(self, eio): s.on('disconnect', handler) s._handle_eio_connect('123', 'environ') s._handle_eio_message('123', '0') - s._handle_eio_disconnect('123') - handler.assert_called_once_with('1') + s._handle_eio_disconnect('123', 'foo') + handler.assert_called_once_with('1', 'foo') + s.manager.disconnect.assert_called_once_with('1', '/', + ignore_queue=True) + assert s.environ == {} + + def test_handle_legacy_disconnect(self, eio): + s = server.Server() + s.manager.disconnect = mock.MagicMock() + handler = mock.MagicMock(side_effect=[TypeError, None]) + s.on('disconnect', handler) + s._handle_eio_connect('123', 'environ') + s._handle_eio_message('123', '0') + s._handle_eio_disconnect('123', 'foo') + handler.assert_called_with('1') s.manager.disconnect.assert_called_once_with('1', '/', ignore_queue=True) assert s.environ == {} @@ -524,9 +537,9 @@ def test_handle_disconnect_namespace(self, eio): s.on('disconnect', handler_namespace, namespace='/foo') s._handle_eio_connect('123', 'environ') s._handle_eio_message('123', '0/foo,') - s._handle_eio_disconnect('123') + s._handle_eio_disconnect('123', 'foo') handler.assert_not_called() - handler_namespace.assert_called_once_with('1') + handler_namespace.assert_called_once_with('1', 'foo') assert s.environ == {} def test_handle_disconnect_only_namespace(self, eio): @@ -539,13 +552,14 @@ def test_handle_disconnect_only_namespace(self, eio): s._handle_eio_message('123', '0/foo,') s._handle_eio_message('123', '1/foo,') assert handler.call_count == 0 - handler_namespace.assert_called_once_with('1') + handler_namespace.assert_called_once_with( + '1', s.reason.CLIENT_DISCONNECT) assert s.environ == {'123': 'environ'} def test_handle_disconnect_unknown_client(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) - s._handle_eio_disconnect('123') + s._handle_eio_disconnect('123', 'foo') def test_handle_event(self, eio): s = server.Server(async_handlers=False) @@ -596,7 +610,8 @@ def test_handle_event_with_catchall_namespace(self, eio): s._handle_eio_message('123', '2/bar,["msg","a","b"]') s._handle_eio_message('123', '2/foo,["my message","a","b","c"]') s._handle_eio_message('123', '2/bar,["my message","a","b","c"]') - s._trigger_event('disconnect', '/bar', sid_bar) + s._trigger_event('disconnect', '/bar', sid_bar, + s.reason.CLIENT_DISCONNECT) connect_star_handler.assert_called_once_with('/bar', sid_bar) msg_foo_handler.assert_called_once_with(sid_foo, 'a', 'b') msg_star_handler.assert_called_once_with('/bar', sid_bar, 'a', 'b') @@ -825,8 +840,8 @@ class MyNamespace(namespace.Namespace): def on_connect(self, sid, environ): result['result'] = (sid, environ) - def on_disconnect(self, sid): - result['result'] = ('disconnect', sid) + def on_disconnect(self, sid, reason): + result['result'] = ('disconnect', sid, reason) def on_foo(self, sid, data): result['result'] = (sid, data) @@ -849,7 +864,8 @@ def on_baz(self, sid, data1, data2): s._handle_eio_message('123', '2/foo,["baz","a","b"]') assert result['result'] == ('a', 'b') s.disconnect('1', '/foo') - assert result['result'] == ('disconnect', '1') + assert result['result'] == ('disconnect', '1', + s.reason.SERVER_DISCONNECT) def test_catchall_namespace_handler(self, eio): result = {} From 06f50f8671c2c3bd9d3d5ba621049c1790fd41e1 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 18 Dec 2024 17:43:08 +0000 Subject: [PATCH 089/173] Release 5.12.0 --- CHANGES.md | 14 ++++++++++++++ pyproject.toml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index c7e0a060..6f7b06d1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,19 @@ # python-socketio change log +**Release 5.12.0** - 2024-12-18 + +- Added a `reason` argument to the disconnect handler [#1422](https://github.com/miguelgrinberg/python-socketio/issues/1422) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/bd8555da8523d1a73432685a00eb5acb4d2261f5)) +- Prevented starting multiple tasks for reconnection [#1369](https://github.com/miguelgrinberg/python-socketio/issues/1369) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/b6ee33e56cf2679664c1b894bf7e5d33a30976db)) (thanks **humayunsr**!) +- Fixed `AsyncClient::wait()` unexpected return after success reconnect [#1407](https://github.com/miguelgrinberg/python-socketio/issues/1407) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/78d1124c50cff149051fb89bf6f08000bf184da5)) (thanks **Arseny**!) +- Removed old constructs from old and unsupported Python versions ([commit](https://github.com/miguelgrinberg/python-socketio/commit/db642bb2bd9794eeceddd54abc47665c69e85406)) +- Removed dependency on unittest.TestCase base class in unit tests ([commit](https://github.com/miguelgrinberg/python-socketio/commit/abf336e108b01f44afb473eb86c1dece6360195c)) +- Adopted pyenv-asyncio for async unit tests ([commit](https://github.com/miguelgrinberg/python-socketio/commit/0b5c4638e5e4bff06fcf46476d218ae5ad4ada14)) +- Adopted unittest.mock.AsyncMock in async unit tests ([commit](https://github.com/miguelgrinberg/python-socketio/commit/8f0e66c1cd1cd63dcef703576cc9cb9c99104df7)) +- Fix typo with `AsyncClient.connect` example [#1403](https://github.com/miguelgrinberg/python-socketio/issues/1403) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/72d37ea79f4cf6076591782e4781fd4868a7e0d6)) (thanks **Peter Bierma**!) +- Documentation typo [#1421](https://github.com/miguelgrinberg/python-socketio/issues/1421) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/bf5a05ae9bf94b2fbd1367a04884ef5a39cd2671)) (thanks **Masen Furer**!) +- Renamed flask-socketio references to python-socketio in documentation [#1377](https://github.com/miguelgrinberg/python-socketio/issues/1377) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/5f83cd0f7b2911705eaf1a8cb9060afbee6eb456)) +- Added Python 3.13 CI builds ([commit](https://github.com/miguelgrinberg/python-socketio/commit/42da5d2f5426e812fd37d4cabcb9277810cae9c1)) + **Release 5.11.4** - 2024-09-02 - Prevent crash when client sends empty event ([commit](https://github.com/miguelgrinberg/python-socketio/commit/1b901de0077322eedb3509318ccba939f5f0bf10)) diff --git a/pyproject.toml b/pyproject.toml index a3ebb6f6..4653b435 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.11.5.dev0" +version = "5.12.0" authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, ] From 2eaa1bd5b17a8a6d15913de10815008ca121cec0 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 18 Dec 2024 17:54:51 +0000 Subject: [PATCH 090/173] Version 5.12.1.dev0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4653b435..ac763bb6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.12.0" +version = "5.12.1.dev0" authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, ] From 7e8e884d50b59d90a2a57ff53df990fcdfbb1b2a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 16:23:18 +0000 Subject: [PATCH 091/173] Bump jinja2 from 3.1.3 to 3.1.5 in /examples/server/wsgi (#1426) #nolog Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.3 to 3.1.5. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.3...3.1.5) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/requirements.txt b/examples/server/wsgi/requirements.txt index 36a36420..dd465d06 100644 --- a/examples/server/wsgi/requirements.txt +++ b/examples/server/wsgi/requirements.txt @@ -5,7 +5,7 @@ eventlet==0.35.2 Flask==1.0.2 greenlet==0.4.12 itsdangerous==1.1.0 -Jinja2==3.1.3 +Jinja2==3.1.5 MarkupSafe==1.1.0 packaging==16.8 pyparsing==2.1.10 From 8964dab9d545333646fafad9aae0becd761a1045 Mon Sep 17 00:00:00 2001 From: Carlos Guerrero Date: Wed, 25 Dec 2024 07:17:31 -0500 Subject: [PATCH 092/173] Fixed broken gevent URL in documentation (#1427) --- docs/intro.rst | 2 +- docs/server.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/intro.rst b/docs/intro.rst index b05e072c..9bb301df 100644 --- a/docs/intro.rst +++ b/docs/intro.rst @@ -189,7 +189,7 @@ Server Features - Can be hosted on any `WSGI `_ or `ASGI `_ web server including `Gunicorn `_, `Uvicorn `_, - `eventlet `_ and `gevent `_. + `eventlet `_ and `gevent `_. - Can be integrated with WSGI applications written in frameworks such as Flask, Django, etc. - Can be integrated with `aiohttp `_, diff --git a/docs/server.rst b/docs/server.rst index ed15ed32..eb91c292 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -838,7 +838,7 @@ Gevent When a multi-threaded web server is unable to satisfy the concurrency and scalability requirements of the application, an option to try is -`Gevent `_. Gevent is a coroutine-based concurrency library +`Gevent `_. Gevent is a coroutine-based concurrency library based on greenlets, which are significantly lighter than threads. Instances of class ``socketio.Server`` will automatically use Gevent if the From b75fd31625cfea0d8c67d776070e4f8de99c1e45 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 29 Dec 2024 18:57:54 +0000 Subject: [PATCH 093/173] Fix admin instrumentation to support disconnect reasons (Fixes #1423) --- src/socketio/admin.py | 82 +++++++++++++++---------------------- src/socketio/async_admin.py | 82 +++++++++++++++---------------------- 2 files changed, 68 insertions(+), 96 deletions(-) diff --git a/src/socketio/admin.py b/src/socketio/admin.py index 58c8aff9..8d6fbcf5 100644 --- a/src/socketio/admin.py +++ b/src/socketio/admin.py @@ -77,13 +77,9 @@ def instrument(self): # track socket connection times self.sio.manager._timestamps = {} - # report socket.io connections - self.sio.manager.__connect = self.sio.manager.connect - self.sio.manager.connect = self._connect - - # report socket.io disconnection - self.sio.manager.__disconnect = self.sio.manager.disconnect - self.sio.manager.disconnect = self._disconnect + # report socket.io connections, disconnections and received events + self.sio.__trigger_event = self.sio._trigger_event + self.sio._trigger_event = self._trigger_event # report join rooms self.sio.manager.__basic_enter_room = \ @@ -99,10 +95,6 @@ def instrument(self): self.sio.manager.__emit = self.sio.manager.emit self.sio.manager.emit = self._emit - # report receive events - self.sio.__handle_event_internal = self.sio._handle_event_internal - self.sio._handle_event_internal = self._handle_event_internal - # report engine.io connections self.sio.eio.on('connect', self._handle_eio_connect) self.sio.eio.on('disconnect', self._handle_eio_disconnect) @@ -128,14 +120,12 @@ def instrument(self): def uninstrument(self): # pragma: no cover if self.mode == 'development': - self.sio.manager.connect = self.sio.manager.__connect - self.sio.manager.disconnect = self.sio.manager.__disconnect + self.sio._trigger_event = self.sio.__trigger_event self.sio.manager.basic_enter_room = \ self.sio.manager.__basic_enter_room self.sio.manager.basic_leave_room = \ self.sio.manager.__basic_leave_room self.sio.manager.emit = self.sio.manager.__emit - self.sio._handle_event_internal = self.sio.__handle_event_internal self.sio.eio._ok = self.sio.eio.__ok from engineio.socket import Socket @@ -205,26 +195,34 @@ def shutdown(self): self.stop_stats_event.set() self.stats_task.join() - def _connect(self, eio_sid, namespace): - sid = self.sio.manager.__connect(eio_sid, namespace) + def _trigger_event(self, event, namespace, *args): t = time.time() - self.sio.manager._timestamps[sid] = t - serialized_socket = self.serialize_socket(sid, namespace, eio_sid) - self.sio.emit('socket_connected', ( - serialized_socket, - datetime.utcfromtimestamp(t).isoformat() + 'Z', - ), namespace=self.admin_namespace) - return sid - - def _disconnect(self, sid, namespace, **kwargs): - del self.sio.manager._timestamps[sid] - self.sio.emit('socket_disconnected', ( - namespace, - sid, - 'N/A', - datetime.utcnow().isoformat() + 'Z', - ), namespace=self.admin_namespace) - return self.sio.manager.__disconnect(sid, namespace, **kwargs) + sid = args[0] + if event == 'connect': + eio_sid = self.sio.manager.eio_sid_from_sid(sid, namespace) + self.sio.manager._timestamps[sid] = t + serialized_socket = self.serialize_socket(sid, namespace, eio_sid) + self.sio.emit('socket_connected', ( + serialized_socket, + datetime.utcfromtimestamp(t).isoformat() + 'Z', + ), namespace=self.admin_namespace) + elif event == 'disconnect': + del self.sio.manager._timestamps[sid] + reason = args[1] + self.sio.emit('socket_disconnected', ( + namespace, + sid, + reason, + datetime.utcfromtimestamp(t).isoformat() + 'Z', + ), namespace=self.admin_namespace) + else: + self.sio.emit('event_received', ( + namespace, + sid, + (event, *args[1:]), + datetime.utcfromtimestamp(t).isoformat() + 'Z', + ), namespace=self.admin_namespace) + return self.sio.__trigger_event(event, namespace, *args) def _check_for_upgrade(self, eio_sid, sid, namespace): # pragma: no cover for _ in range(5): @@ -269,7 +267,7 @@ def _emit(self, event, data, namespace, room=None, skip_sid=None, **kwargs) if namespace != self.admin_namespace: event_data = [event] + list(data) if isinstance(data, tuple) \ - else [data] + else [event, data] if not isinstance(skip_sid, list): # pragma: no branch skip_sid = [skip_sid] for sid, _ in self.sio.manager.get_participants(namespace, room): @@ -282,18 +280,6 @@ def _emit(self, event, data, namespace, room=None, skip_sid=None, ), namespace=self.admin_namespace) return ret - def _handle_event_internal(self, server, sid, eio_sid, data, namespace, - id): - ret = self.sio.__handle_event_internal(server, sid, eio_sid, data, - namespace, id) - self.sio.emit('event_received', ( - namespace, - sid, - data, - datetime.utcnow().isoformat() + 'Z', - ), namespace=self.admin_namespace) - return ret - def _handle_eio_connect(self, eio_sid, environ): if self.stop_stats_event is None: self.stop_stats_event = self.sio.eio.create_event() @@ -303,9 +289,9 @@ def _handle_eio_connect(self, eio_sid, environ): self.event_buffer.push('rawConnection') return self.sio._handle_eio_connect(eio_sid, environ) - def _handle_eio_disconnect(self, eio_sid): + def _handle_eio_disconnect(self, eio_sid, reason): self.event_buffer.push('rawDisconnection') - return self.sio._handle_eio_disconnect(eio_sid) + return self.sio._handle_eio_disconnect(eio_sid, reason) def _eio_http_response(self, packets=None, headers=None, jsonp_index=None): ret = self.sio.eio.__ok(packets=packets, headers=headers, diff --git a/src/socketio/async_admin.py b/src/socketio/async_admin.py index 162c5660..68ea289c 100644 --- a/src/socketio/async_admin.py +++ b/src/socketio/async_admin.py @@ -58,13 +58,9 @@ def instrument(self): # track socket connection times self.sio.manager._timestamps = {} - # report socket.io connections - self.sio.manager.__connect = self.sio.manager.connect - self.sio.manager.connect = self._connect - - # report socket.io disconnection - self.sio.manager.__disconnect = self.sio.manager.disconnect - self.sio.manager.disconnect = self._disconnect + # report socket.io connections, disconnections and received events + self.sio.__trigger_event = self.sio._trigger_event + self.sio._trigger_event = self._trigger_event # report join rooms self.sio.manager.__basic_enter_room = \ @@ -80,10 +76,6 @@ def instrument(self): self.sio.manager.__emit = self.sio.manager.emit self.sio.manager.emit = self._emit - # report receive events - self.sio.__handle_event_internal = self.sio._handle_event_internal - self.sio._handle_event_internal = self._handle_event_internal - # report engine.io connections self.sio.eio.on('connect', self._handle_eio_connect) self.sio.eio.on('disconnect', self._handle_eio_disconnect) @@ -109,14 +101,12 @@ def instrument(self): def uninstrument(self): # pragma: no cover if self.mode == 'development': - self.sio.manager.connect = self.sio.manager.__connect - self.sio.manager.disconnect = self.sio.manager.__disconnect + self.sio._trigger_event = self.sio.__trigger_event self.sio.manager.basic_enter_room = \ self.sio.manager.__basic_enter_room self.sio.manager.basic_leave_room = \ self.sio.manager.__basic_leave_room self.sio.manager.emit = self.sio.manager.__emit - self.sio._handle_event_internal = self.sio.__handle_event_internal self.sio.eio._ok = self.sio.eio.__ok from engineio.async_socket import AsyncSocket @@ -193,26 +183,34 @@ async def shutdown(self): self.stop_stats_event.set() await asyncio.gather(self.stats_task) - async def _connect(self, eio_sid, namespace): - sid = await self.sio.manager.__connect(eio_sid, namespace) + async def _trigger_event(self, event, namespace, *args): t = time.time() - self.sio.manager._timestamps[sid] = t - serialized_socket = self.serialize_socket(sid, namespace, eio_sid) - await self.sio.emit('socket_connected', ( - serialized_socket, - datetime.utcfromtimestamp(t).isoformat() + 'Z', - ), namespace=self.admin_namespace) - return sid - - async def _disconnect(self, sid, namespace, **kwargs): - del self.sio.manager._timestamps[sid] - await self.sio.emit('socket_disconnected', ( - namespace, - sid, - 'N/A', - datetime.utcnow().isoformat() + 'Z', - ), namespace=self.admin_namespace) - return await self.sio.manager.__disconnect(sid, namespace, **kwargs) + sid = args[0] + if event == 'connect': + eio_sid = self.sio.manager.eio_sid_from_sid(sid, namespace) + self.sio.manager._timestamps[sid] = t + serialized_socket = self.serialize_socket(sid, namespace, eio_sid) + await self.sio.emit('socket_connected', ( + serialized_socket, + datetime.utcfromtimestamp(t).isoformat() + 'Z', + ), namespace=self.admin_namespace) + elif event == 'disconnect': + del self.sio.manager._timestamps[sid] + reason = args[1] + await self.sio.emit('socket_disconnected', ( + namespace, + sid, + reason, + datetime.utcfromtimestamp(t).isoformat() + 'Z', + ), namespace=self.admin_namespace) + else: + await self.sio.emit('event_received', ( + namespace, + sid, + (event, *args[1:]), + datetime.utcfromtimestamp(t).isoformat() + 'Z', + ), namespace=self.admin_namespace) + return await self.sio.__trigger_event(event, namespace, *args) async def _check_for_upgrade(self, eio_sid, sid, namespace): # pragma: no cover @@ -258,7 +256,7 @@ async def _emit(self, event, data, namespace, room=None, skip_sid=None, callback=callback, **kwargs) if namespace != self.admin_namespace: event_data = [event] + list(data) if isinstance(data, tuple) \ - else [data] + else [event, data] if not isinstance(skip_sid, list): # pragma: no branch skip_sid = [skip_sid] for sid, _ in self.sio.manager.get_participants(namespace, room): @@ -271,18 +269,6 @@ async def _emit(self, event, data, namespace, room=None, skip_sid=None, ), namespace=self.admin_namespace) return ret - async def _handle_event_internal(self, server, sid, eio_sid, data, - namespace, id): - ret = await self.sio.__handle_event_internal(server, sid, eio_sid, - data, namespace, id) - await self.sio.emit('event_received', ( - namespace, - sid, - data, - datetime.utcnow().isoformat() + 'Z', - ), namespace=self.admin_namespace) - return ret - async def _handle_eio_connect(self, eio_sid, environ): if self.stop_stats_event is None: self.stop_stats_event = self.sio.eio.create_event() @@ -292,9 +278,9 @@ async def _handle_eio_connect(self, eio_sid, environ): self.event_buffer.push('rawConnection') return await self.sio._handle_eio_connect(eio_sid, environ) - async def _handle_eio_disconnect(self, eio_sid): + async def _handle_eio_disconnect(self, eio_sid, reason): self.event_buffer.push('rawDisconnection') - return await self.sio._handle_eio_disconnect(eio_sid) + return await self.sio._handle_eio_disconnect(eio_sid, reason) def _eio_http_response(self, packets=None, headers=None, jsonp_index=None): ret = self.sio.eio.__ok(packets=packets, headers=headers, From 8fe012abbb350107b742ab2cf9aa44d328bc23e9 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 29 Dec 2024 19:54:27 +0000 Subject: [PATCH 094/173] Stop using deprecated datetime functions --- src/socketio/admin.py | 18 +++++++++--------- src/socketio/async_admin.py | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/socketio/admin.py b/src/socketio/admin.py index 8d6fbcf5..12b905ea 100644 --- a/src/socketio/admin.py +++ b/src/socketio/admin.py @@ -1,4 +1,4 @@ -from datetime import datetime +from datetime import datetime, timezone import functools import os import socket @@ -204,7 +204,7 @@ def _trigger_event(self, event, namespace, *args): serialized_socket = self.serialize_socket(sid, namespace, eio_sid) self.sio.emit('socket_connected', ( serialized_socket, - datetime.utcfromtimestamp(t).isoformat() + 'Z', + datetime.fromtimestamp(t, timezone.utc).isoformat(), ), namespace=self.admin_namespace) elif event == 'disconnect': del self.sio.manager._timestamps[sid] @@ -213,14 +213,14 @@ def _trigger_event(self, event, namespace, *args): namespace, sid, reason, - datetime.utcfromtimestamp(t).isoformat() + 'Z', + datetime.fromtimestamp(t, timezone.utc).isoformat(), ), namespace=self.admin_namespace) else: self.sio.emit('event_received', ( namespace, sid, (event, *args[1:]), - datetime.utcfromtimestamp(t).isoformat() + 'Z', + datetime.fromtimestamp(t, timezone.utc).isoformat(), ), namespace=self.admin_namespace) return self.sio.__trigger_event(event, namespace, *args) @@ -246,7 +246,7 @@ def _basic_enter_room(self, sid, namespace, room, eio_sid=None): namespace, room, sid, - datetime.utcnow().isoformat() + 'Z', + datetime.now(timezone.utc).isoformat(), ), namespace=self.admin_namespace) return ret @@ -256,7 +256,7 @@ def _basic_leave_room(self, sid, namespace, room): namespace, room, sid, - datetime.utcnow().isoformat() + 'Z', + datetime.now(timezone.utc).isoformat(), ), namespace=self.admin_namespace) return self.sio.manager.__basic_leave_room(sid, namespace, room) @@ -276,7 +276,7 @@ def _emit(self, event, data, namespace, room=None, skip_sid=None, namespace, sid, event_data, - datetime.utcnow().isoformat() + 'Z', + datetime.now(timezone.utc).isoformat(), ), namespace=self.admin_namespace) return ret @@ -335,7 +335,7 @@ def _eio_send_ping(socket, self): # pragma: no cover eio_sid) self.sio.emit('socket_connected', ( serialized_socket, - datetime.utcfromtimestamp(t).isoformat() + 'Z', + datetime.fromtimestamp(t, timezone.utc).isoformat(), ), namespace=self.admin_namespace) return socket.__send_ping() @@ -384,7 +384,7 @@ def serialize_socket(self, sid, namespace, eio_sid=None): 'secure': environ.get('wsgi.url_scheme', '') == 'https', 'url': environ.get('PATH_INFO', ''), 'issued': tm * 1000, - 'time': datetime.utcfromtimestamp(tm).isoformat() + 'Z' + 'time': datetime.fromtimestamp(tm, timezone.utc).isoformat() if tm else '', }, 'rooms': self.sio.manager.get_rooms(sid, namespace), diff --git a/src/socketio/async_admin.py b/src/socketio/async_admin.py index 68ea289c..b052d8fe 100644 --- a/src/socketio/async_admin.py +++ b/src/socketio/async_admin.py @@ -1,5 +1,5 @@ import asyncio -from datetime import datetime +from datetime import datetime, timezone import functools import os import socket @@ -192,7 +192,7 @@ async def _trigger_event(self, event, namespace, *args): serialized_socket = self.serialize_socket(sid, namespace, eio_sid) await self.sio.emit('socket_connected', ( serialized_socket, - datetime.utcfromtimestamp(t).isoformat() + 'Z', + datetime.fromtimestamp(t, timezone.utc).isoformat(), ), namespace=self.admin_namespace) elif event == 'disconnect': del self.sio.manager._timestamps[sid] @@ -201,14 +201,14 @@ async def _trigger_event(self, event, namespace, *args): namespace, sid, reason, - datetime.utcfromtimestamp(t).isoformat() + 'Z', + datetime.fromtimestamp(t, timezone.utc).isoformat(), ), namespace=self.admin_namespace) else: await self.sio.emit('event_received', ( namespace, sid, (event, *args[1:]), - datetime.utcfromtimestamp(t).isoformat() + 'Z', + datetime.fromtimestamp(t, timezone.utc).isoformat(), ), namespace=self.admin_namespace) return await self.sio.__trigger_event(event, namespace, *args) @@ -235,7 +235,7 @@ def _basic_enter_room(self, sid, namespace, room, eio_sid=None): namespace, room, sid, - datetime.utcnow().isoformat() + 'Z', + datetime.now(timezone.utc).isoformat(), ))) return ret @@ -245,7 +245,7 @@ def _basic_leave_room(self, sid, namespace, room): namespace, room, sid, - datetime.utcnow().isoformat() + 'Z', + datetime.now(timezone.utc).isoformat(), ))) return self.sio.manager.__basic_leave_room(sid, namespace, room) @@ -265,7 +265,7 @@ async def _emit(self, event, data, namespace, room=None, skip_sid=None, namespace, sid, event_data, - datetime.utcnow().isoformat() + 'Z', + datetime.now(timezone.utc).isoformat(), ), namespace=self.admin_namespace) return ret @@ -324,7 +324,7 @@ async def _eio_send_ping(socket, self): # pragma: no cover eio_sid) await self.sio.emit('socket_connected', ( serialized_socket, - datetime.utcfromtimestamp(t).isoformat() + 'Z', + datetime.fromtimestamp(t, timezone.utc).isoformat(), ), namespace=self.admin_namespace) return await socket.__send_ping() @@ -377,7 +377,7 @@ def serialize_socket(self, sid, namespace, eio_sid=None): 'secure': environ.get('wsgi.url_scheme', '') == 'https', 'url': environ.get('PATH_INFO', ''), 'issued': tm * 1000, - 'time': datetime.utcfromtimestamp(tm).isoformat() + 'Z' + 'time': datetime.fromtimestamp(tm, timezone.utc).isoformat() if tm else '', }, 'rooms': self.sio.manager.get_rooms(sid, namespace), From 269332da8041df115e3a1e2ca04808c3179a72e1 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 29 Dec 2024 20:03:59 +0000 Subject: [PATCH 095/173] Enable instrumentation by default in WSGI and ASGI examples --- examples/server/asgi/app.py | 12 +++++++++++- examples/server/wsgi/app.py | 12 +++++++++++- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/examples/server/asgi/app.py b/examples/server/asgi/app.py index d549ab09..996dc272 100644 --- a/examples/server/asgi/app.py +++ b/examples/server/asgi/app.py @@ -2,7 +2,7 @@ # set instrument to `True` to accept connections from the official Socket.IO # Admin UI hosted at https://admin.socket.io -instrument = False +instrument = True admin_login = { 'username': 'admin', 'password': 'python', # change this to a strong secret for production use! @@ -93,4 +93,14 @@ def test_disconnect(sid, reason): if __name__ == '__main__': + if instrument: + print('The server is instrumented for remote administration.') + print( + 'Use the official Socket.IO Admin UI at https://admin.socket.io ' + 'with the following connection details:' + ) + print(' - Server URL: http://localhost:5000') + print(' - Username:', admin_login['username']) + print(' - Password:', admin_login['password']) + print('') uvicorn.run(app, host='127.0.0.1', port=5000) diff --git a/examples/server/wsgi/app.py b/examples/server/wsgi/app.py index 62bd59b1..7fc871b4 100644 --- a/examples/server/wsgi/app.py +++ b/examples/server/wsgi/app.py @@ -5,7 +5,7 @@ # set instrument to `True` to accept connections from the official Socket.IO # Admin UI hosted at https://admin.socket.io -instrument = False +instrument = True admin_login = { 'username': 'admin', 'password': 'python', # change this to a strong secret for production use! @@ -99,6 +99,16 @@ def disconnect(sid, reason): if __name__ == '__main__': + if instrument: + print('The server is instrumented for remote administration.') + print( + 'Use the official Socket.IO Admin UI at https://admin.socket.io ' + 'with the following connection details:' + ) + print(' - Server URL: http://localhost:5000') + print(' - Username:', admin_login['username']) + print(' - Password:', admin_login['password']) + print('') if sio.async_mode == 'threading': # deploy with Werkzeug app.run(threaded=True) From 572648bd0cb18b3c40a9f665a3bd6835730c53fd Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 29 Dec 2024 20:07:59 +0000 Subject: [PATCH 096/173] Release 5.12.1 --- CHANGES.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 6f7b06d1..53afe357 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # python-socketio change log +**Release 5.12.1** - 2024-12-29 + +- Fix admin instrumentation support of disconnect reasons [#1423](https://github.com/miguelgrinberg/python-socketio/issues/1423) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/b75fd31625cfea0d8c67d776070e4f8de99c1e45)) +- Stop using deprecated datetime functions ([commit](https://github.com/miguelgrinberg/python-socketio/commit/8fe012abbb350107b742ab2cf9aa44d328bc23e9)) +- Enable admin instrumentation by default in WSGI and ASGI examples ([commit](https://github.com/miguelgrinberg/python-socketio/commit/269332da8041df115e3a1e2ca04808c3179a72e1)) +- Fixed broken gevent URL in documentation [#1427](https://github.com/miguelgrinberg/python-socketio/issues/1427) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/8964dab9d545333646fafad9aae0becd761a1045)) (thanks **Carlos Guerrero**!) + **Release 5.12.0** - 2024-12-18 - Added a `reason` argument to the disconnect handler [#1422](https://github.com/miguelgrinberg/python-socketio/issues/1422) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/bd8555da8523d1a73432685a00eb5acb4d2261f5)) diff --git a/pyproject.toml b/pyproject.toml index ac763bb6..02d3e9b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.12.1.dev0" +version = "5.12.1" authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, ] From 387f996bdb9a4effc6d8a358410f04b1199e527c Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 29 Dec 2024 20:11:53 +0000 Subject: [PATCH 097/173] Version 5.12.2.dev0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 02d3e9b5..c3c629d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.12.1" +version = "5.12.2.dev0" authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, ] From 9e43a39cfe562b1c13035f2017cd9d2f78e51f8d Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Mon, 6 Jan 2025 17:47:15 +0000 Subject: [PATCH 098/173] revert to default funding file #nolog --- .github/FUNDING.yml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 527fcb95..00000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,4 +0,0 @@ -github: miguelgrinberg -patreon: miguelgrinberg -open_collective: python-socketio -custom: https://paypal.me/miguelgrinberg From a598a55cc178268d78274f433bb330fb756bf0a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 16:23:47 +0000 Subject: [PATCH 099/173] Bump django in /examples/server/wsgi/django_socketio (#1430) #nolog Bumps [django](https://github.com/django/django) from 4.2.17 to 4.2.18. - [Commits](https://github.com/django/django/compare/4.2.17...4.2.18) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/django_socketio/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/django_socketio/requirements.txt b/examples/server/wsgi/django_socketio/requirements.txt index 98d2cf1f..975beff1 100644 --- a/examples/server/wsgi/django_socketio/requirements.txt +++ b/examples/server/wsgi/django_socketio/requirements.txt @@ -1,6 +1,6 @@ asgiref==3.6.0 bidict==0.22.1 -Django==4.2.17 +Django==4.2.18 gunicorn==22.0.0 h11==0.14.0 python-engineio From 7605630bb236b4baf98574ca2a8f0cdba2696ef4 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 5 Feb 2025 23:56:45 +0000 Subject: [PATCH 100/173] Allow custom client subclasses to be used in SimpleClient and AsyncSimpleClient (Fixes #1432) --- src/socketio/async_simple_client.py | 5 ++- src/socketio/simple_client.py | 5 ++- tests/async/test_simple_client.py | 61 ++++++++++++++++------------- tests/common/test_simple_client.py | 44 +++++++++++++-------- 4 files changed, 68 insertions(+), 47 deletions(-) diff --git a/src/socketio/async_simple_client.py b/src/socketio/async_simple_client.py index c6cd4fc1..adac6ead 100644 --- a/src/socketio/async_simple_client.py +++ b/src/socketio/async_simple_client.py @@ -12,6 +12,8 @@ class AsyncSimpleClient: The positional and keyword arguments given in the constructor are passed to the underlying :func:`socketio.AsyncClient` object. """ + client_class = AsyncClient + def __init__(self, *args, **kwargs): self.client_args = args self.client_kwargs = kwargs @@ -60,7 +62,8 @@ async def connect(self, url, headers={}, auth=None, transports=None, self.namespace = namespace self.input_buffer = [] self.input_event.clear() - self.client = AsyncClient(*self.client_args, **self.client_kwargs) + self.client = self.client_class( + *self.client_args, **self.client_kwargs) @self.client.event(namespace=self.namespace) def connect(): # pragma: no cover diff --git a/src/socketio/simple_client.py b/src/socketio/simple_client.py index 67791477..3f046b4b 100644 --- a/src/socketio/simple_client.py +++ b/src/socketio/simple_client.py @@ -12,6 +12,8 @@ class SimpleClient: The positional and keyword arguments given in the constructor are passed to the underlying :func:`socketio.Client` object. """ + client_class = Client + def __init__(self, *args, **kwargs): self.client_args = args self.client_kwargs = kwargs @@ -58,7 +60,8 @@ def connect(self, url, headers={}, auth=None, transports=None, self.namespace = namespace self.input_buffer = [] self.input_event.clear() - self.client = Client(*self.client_args, **self.client_kwargs) + self.client = self.client_class( + *self.client_args, **self.client_kwargs) @self.client.event(namespace=self.namespace) def connect(): # pragma: no cover diff --git a/tests/async/test_simple_client.py b/tests/async/test_simple_client.py index 08926922..bfe2a90f 100644 --- a/tests/async/test_simple_client.py +++ b/tests/async/test_simple_client.py @@ -16,46 +16,51 @@ async def test_constructor(self): assert not client.connected async def test_connect(self): + mock_client = mock.MagicMock() + original_client_class = AsyncSimpleClient.client_class + AsyncSimpleClient.client_class = mock_client + client = AsyncSimpleClient(123, a='b') - with mock.patch('socketio.async_simple_client.AsyncClient') \ - as mock_client: + mock_client.return_value.connect = mock.AsyncMock() + + await client.connect('url', headers='h', auth='a', transports='t', + namespace='n', socketio_path='s', + wait_timeout='w') + mock_client.assert_called_once_with(123, a='b') + assert client.client == mock_client() + mock_client().connect.assert_awaited_once_with( + 'url', headers='h', auth='a', transports='t', + namespaces=['n'], socketio_path='s', wait_timeout='w') + mock_client().event.call_count == 3 + mock_client().on.assert_called_once_with('*', namespace='n') + assert client.namespace == 'n' + assert not client.input_event.is_set() + + AsyncSimpleClient.client_class = original_client_class + + async def test_connect_context_manager(self): + mock_client = mock.MagicMock() + original_client_class = AsyncSimpleClient.client_class + AsyncSimpleClient.client_class = mock_client + + async with AsyncSimpleClient(123, a='b') as client: mock_client.return_value.connect = mock.AsyncMock() - await client.connect('url', headers='h', auth='a', transports='t', - namespace='n', socketio_path='s', - wait_timeout='w') + await client.connect('url', headers='h', auth='a', + transports='t', namespace='n', + socketio_path='s', wait_timeout='w') mock_client.assert_called_once_with(123, a='b') assert client.client == mock_client() mock_client().connect.assert_awaited_once_with( 'url', headers='h', auth='a', transports='t', namespaces=['n'], socketio_path='s', wait_timeout='w') mock_client().event.call_count == 3 - mock_client().on.assert_called_once_with('*', namespace='n') + mock_client().on.assert_called_once_with( + '*', namespace='n') assert client.namespace == 'n' assert not client.input_event.is_set() - async def test_connect_context_manager(self): - async def _t(): - async with AsyncSimpleClient(123, a='b') as client: - with mock.patch('socketio.async_simple_client.AsyncClient') \ - as mock_client: - mock_client.return_value.connect = mock.AsyncMock() - - await client.connect('url', headers='h', auth='a', - transports='t', namespace='n', - socketio_path='s', wait_timeout='w') - mock_client.assert_called_once_with(123, a='b') - assert client.client == mock_client() - mock_client().connect.assert_awaited_once_with( - 'url', headers='h', auth='a', transports='t', - namespaces=['n'], socketio_path='s', wait_timeout='w') - mock_client().event.call_count == 3 - mock_client().on.assert_called_once_with( - '*', namespace='n') - assert client.namespace == 'n' - assert not client.input_event.is_set() - - await _t() + AsyncSimpleClient.client_class = original_client_class async def test_connect_twice(self): client = AsyncSimpleClient(123, a='b') diff --git a/tests/common/test_simple_client.py b/tests/common/test_simple_client.py index 42790573..b17afbcc 100644 --- a/tests/common/test_simple_client.py +++ b/tests/common/test_simple_client.py @@ -14,10 +14,34 @@ def test_constructor(self): assert not client.connected def test_connect(self): + mock_client = mock.MagicMock() + original_client_class = SimpleClient.client_class + SimpleClient.client_class = mock_client + client = SimpleClient(123, a='b') - with mock.patch('socketio.simple_client.Client') as mock_client: + client.connect('url', headers='h', auth='a', transports='t', + namespace='n', socketio_path='s', wait_timeout='w') + mock_client.assert_called_once_with(123, a='b') + assert client.client == mock_client() + mock_client().connect.assert_called_once_with( + 'url', headers='h', auth='a', transports='t', + namespaces=['n'], socketio_path='s', wait_timeout='w') + mock_client().event.call_count == 3 + mock_client().on.assert_called_once_with('*', namespace='n') + assert client.namespace == 'n' + assert not client.input_event.is_set() + + SimpleClient.client_class = original_client_class + + def test_connect_context_manager(self): + mock_client = mock.MagicMock() + original_client_class = SimpleClient.client_class + SimpleClient.client_class = mock_client + + with SimpleClient(123, a='b') as client: client.connect('url', headers='h', auth='a', transports='t', - namespace='n', socketio_path='s', wait_timeout='w') + namespace='n', socketio_path='s', + wait_timeout='w') mock_client.assert_called_once_with(123, a='b') assert client.client == mock_client() mock_client().connect.assert_called_once_with( @@ -28,21 +52,7 @@ def test_connect(self): assert client.namespace == 'n' assert not client.input_event.is_set() - def test_connect_context_manager(self): - with SimpleClient(123, a='b') as client: - with mock.patch('socketio.simple_client.Client') as mock_client: - client.connect('url', headers='h', auth='a', transports='t', - namespace='n', socketio_path='s', - wait_timeout='w') - mock_client.assert_called_once_with(123, a='b') - assert client.client == mock_client() - mock_client().connect.assert_called_once_with( - 'url', headers='h', auth='a', transports='t', - namespaces=['n'], socketio_path='s', wait_timeout='w') - mock_client().event.call_count == 3 - mock_client().on.assert_called_once_with('*', namespace='n') - assert client.namespace == 'n' - assert not client.input_event.is_set() + SimpleClient.client_class = original_client_class def test_connect_twice(self): client = SimpleClient(123, a='b') From 82462f011c70b749270362bd3c352d7e3e155cfd Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Thu, 27 Feb 2025 12:35:57 +0000 Subject: [PATCH 101/173] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 5a45f90c..d73d5053 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -23,7 +23,7 @@ Steps to reproduce the behavior: A clear and concise description of what you expected to happen. **Logs** -Please provide relevant logs from the server and the client. On the Python server and client, add the `logger=True` and `engineio_logger=True` arguments to your `Server()` or `Client()` objects to get logs dumped on your terminal. If you are using the JavaScript client, see [here](https://socket.io/docs/logging-and-debugging/) for how to enable logs. +Please provide relevant logs from the server and the client. On the Python server and client, add the `logger=True` and `engineio_logger=True` arguments to your `Server()` or `Client()` objects to get logs dumped on your terminal. If you are using the JavaScript client, see [here](https://socket.io/docs/v4/logging-and-debugging/) for how to enable logs. **Additional context** Add any other context about the problem here. From 781dc9a0305f6a795a0467ebc795f4b765084a0d Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Thu, 27 Feb 2025 12:36:30 +0000 Subject: [PATCH 102/173] Update feature_request.md --- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 32257912..968c279f 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -17,7 +17,7 @@ A clear and concise description of what you want to happen. A clear and concise description of any alternative solutions or features you've considered. **Logs** -Please provide relevant logs from the server and the client. On the Python server and client, add the `logger=True` and `engineio_logger=True` arguments to your `Server()` or `Client()` objects to get logs dumped on your terminal. If you are using the JavaScript client, see [here](https://socket.io/docs/logging-and-debugging/) for how to enable logs. +Please provide relevant logs from the server and the client. On the Python server and client, add the `logger=True` and `engineio_logger=True` arguments to your `Server()` or `Client()` objects to get logs dumped on your terminal. If you are using the JavaScript client, see [here](https://socket.io/docs/v4/logging-and-debugging/) for how to enable logs. **Additional context** Add any other context or screenshots about the feature request here. From 1b4194360307f45166c5be96ca37e8edf213d91c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Mar 2025 23:12:50 +0000 Subject: [PATCH 103/173] Bump django in /examples/server/wsgi/django_socketio (#1439) #nolog Bumps [django](https://github.com/django/django) from 4.2.18 to 4.2.20. - [Commits](https://github.com/django/django/compare/4.2.18...4.2.20) --- updated-dependencies: - dependency-name: django dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/django_socketio/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/django_socketio/requirements.txt b/examples/server/wsgi/django_socketio/requirements.txt index 975beff1..f3edf414 100644 --- a/examples/server/wsgi/django_socketio/requirements.txt +++ b/examples/server/wsgi/django_socketio/requirements.txt @@ -1,6 +1,6 @@ asgiref==3.6.0 bidict==0.22.1 -Django==4.2.18 +Django==4.2.20 gunicorn==22.0.0 h11==0.14.0 python-engineio From 4adebf6ab822083013fc5b4e43d2d2bdb2c5048b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 7 Mar 2025 09:57:09 +0000 Subject: [PATCH 104/173] Bump jinja2 from 3.1.5 to 3.1.6 in /examples/server/wsgi (#1440) #nolog Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.5 to 3.1.6. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.5...3.1.6) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/requirements.txt b/examples/server/wsgi/requirements.txt index dd465d06..3026d7f9 100644 --- a/examples/server/wsgi/requirements.txt +++ b/examples/server/wsgi/requirements.txt @@ -5,7 +5,7 @@ eventlet==0.35.2 Flask==1.0.2 greenlet==0.4.12 itsdangerous==1.1.0 -Jinja2==3.1.5 +Jinja2==3.1.6 MarkupSafe==1.1.0 packaging==16.8 pyparsing==2.1.10 From 288ebb189d799a05bbc5979a834433034ea2939f Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 9 Mar 2025 14:45:32 +0000 Subject: [PATCH 105/173] Eliminate race conditions on disconnect (Fixes #1441) --- src/socketio/async_client.py | 3 ++- src/socketio/client.py | 2 +- tests/async/test_client.py | 4 ++-- tests/common/test_client.py | 4 ++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index 463073e7..c7f5e86e 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -191,6 +191,7 @@ async def wait(self): if not self._reconnect_task: if self.eio.state == 'connected': # pragma: no cover # connected while sleeping above + print('oops') continue break await self._reconnect_task @@ -324,7 +325,7 @@ async def disconnect(self): for n in self.namespaces: await self._send_packet(self.packet_class(packet.DISCONNECT, namespace=n)) - await self.eio.disconnect(abort=True) + await self.eio.disconnect() async def shutdown(self): """Stop the client. diff --git a/src/socketio/client.py b/src/socketio/client.py index ade2dd6f..746d2c4c 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -306,7 +306,7 @@ def disconnect(self): for n in self.namespaces: self._send_packet(self.packet_class( packet.DISCONNECT, namespace=n)) - self.eio.disconnect(abort=True) + self.eio.disconnect() def shutdown(self): """Stop the client. diff --git a/tests/async/test_client.py b/tests/async/test_client.py index 3eec1a88..d7e0f9e7 100644 --- a/tests/async/test_client.py +++ b/tests/async/test_client.py @@ -475,7 +475,7 @@ async def test_disconnect(self): c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) - c.eio.disconnect.assert_awaited_once_with(abort=True) + c.eio.disconnect.assert_awaited_once_with() async def test_disconnect_namespaces(self): c = async_client.AsyncClient() @@ -993,7 +993,7 @@ async def test_shutdown_disconnect(self): c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) - c.eio.disconnect.assert_awaited_once_with(abort=True) + c.eio.disconnect.assert_awaited_once_with() async def test_shutdown_disconnect_namespaces(self): c = async_client.AsyncClient() diff --git a/tests/common/test_client.py b/tests/common/test_client.py index ac930c79..7ee2bacf 100644 --- a/tests/common/test_client.py +++ b/tests/common/test_client.py @@ -633,7 +633,7 @@ def test_disconnect(self): c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) - c.eio.disconnect.assert_called_once_with(abort=True) + c.eio.disconnect.assert_called_once_with() def test_disconnect_namespaces(self): c = client.Client() @@ -1138,7 +1138,7 @@ def test_shutdown_disconnect(self): c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) - c.eio.disconnect.assert_called_once_with(abort=True) + c.eio.disconnect.assert_called_once_with() def test_shutdown_disconnect_namespaces(self): c = client.Client() From fc0c1e2fee32a4fce97f03ce7451a320a91d1d82 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 22 Mar 2025 12:41:30 +0000 Subject: [PATCH 106/173] Bump gunicorn in /examples/server/wsgi/django_socketio (#1445) #nolog Bumps [gunicorn](https://github.com/benoitc/gunicorn) from 22.0.0 to 23.0.0. - [Release notes](https://github.com/benoitc/gunicorn/releases) - [Commits](https://github.com/benoitc/gunicorn/compare/22.0.0...23.0.0) --- updated-dependencies: - dependency-name: gunicorn dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/django_socketio/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/django_socketio/requirements.txt b/examples/server/wsgi/django_socketio/requirements.txt index f3edf414..8f6c19dd 100644 --- a/examples/server/wsgi/django_socketio/requirements.txt +++ b/examples/server/wsgi/django_socketio/requirements.txt @@ -1,7 +1,7 @@ asgiref==3.6.0 bidict==0.22.1 Django==4.2.20 -gunicorn==22.0.0 +gunicorn==23.0.0 h11==0.14.0 python-engineio python-socketio From 537630b983245cc137f609c3e6247d6d68ebdea5 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 5 Apr 2025 23:29:13 +0100 Subject: [PATCH 107/173] Remove incorrect reference to an "asyncio" installation extra (Fixes #1449) --- docs/server.rst | 6 ------ 1 file changed, 6 deletions(-) diff --git a/docs/server.rst b/docs/server.rst index eb91c292..9432a172 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -19,12 +19,6 @@ command:: pip install python-socketio -If you plan to build an asynchronous web server based on the ``asyncio`` -package, then you can install this package and some additional dependencies -that are needed with:: - - pip install "python-socketio[asyncio]" - Creating a Server Instance -------------------------- From 6a52e8b50274a7524fadcd2633eb819811a63734 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 6 Apr 2025 16:15:19 +0100 Subject: [PATCH 108/173] Add support for Redis Sentinel (#1448) * Add support for Redis Sentinel * more unit tests --- src/socketio/async_redis_manager.py | 22 ++++++++++--- src/socketio/redis_manager.py | 48 ++++++++++++++++++++++++++--- tests/common/test_redis_manager.py | 38 +++++++++++++++++++++++ 3 files changed, 98 insertions(+), 10 deletions(-) create mode 100644 tests/common/test_redis_manager.py diff --git a/src/socketio/async_redis_manager.py b/src/socketio/async_redis_manager.py index e039c6e9..cc82f4a5 100644 --- a/src/socketio/async_redis_manager.py +++ b/src/socketio/async_redis_manager.py @@ -13,6 +13,7 @@ RedisError = None from .async_pubsub_manager import AsyncPubSubManager +from .redis_manager import parse_redis_sentinel_url class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover @@ -29,15 +30,18 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover client_manager=socketio.AsyncRedisManager(url)) :param url: The connection URL for the Redis server. For a default Redis - store running on the same host, use ``redis://``. To use an - SSL connection, use ``rediss://``. + store running on the same host, use ``redis://``. To use a + TLS connection, use ``rediss://``. To use Redis Sentinel, use + ``redis+sentinel://`` with a comma-separated list of hosts + and the service name after the db in the URL path. Example: + ``redis+sentinel://user:pw@host1:1234,host2:2345/0/myredis``. :param channel: The channel name on which the server sends and receives notifications. Must be the same in all the servers. :param write_only: If set to ``True``, only initialize to emit events. The default of ``False`` initializes the class for emitting and receiving. :param redis_options: additional keyword arguments to be passed to - ``aioredis.from_url()``. + ``Redis.from_url()`` or ``Sentinel()``. """ name = 'aioredis' @@ -54,8 +58,16 @@ def __init__(self, url='redis://localhost:6379/0', channel='socketio', super().__init__(channel=channel, write_only=write_only, logger=logger) def _redis_connect(self): - self.redis = aioredis.Redis.from_url(self.redis_url, - **self.redis_options) + if not self.redis_url.startswith('redis+sentinel://'): + self.redis = aioredis.Redis.from_url(self.redis_url, + **self.redis_options) + else: + sentinels, service_name, connection_kwargs = \ + parse_redis_sentinel_url(self.redis_url) + kwargs = self.redis_options + kwargs.update(connection_kwargs) + sentinel = aioredis.sentinel.Sentinel(sentinels, **kwargs) + self.redis = sentinel.master_for(service_name or self.channel) self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True) async def _publish(self, data): diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index a16fb2c7..df98618c 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -1,6 +1,7 @@ import logging import pickle import time +from urllib.parse import urlparse try: import redis @@ -12,6 +13,32 @@ logger = logging.getLogger('socketio') +def parse_redis_sentinel_url(url): + """Parse a Redis Sentinel URL with the format: + redis+sentinel://[:password]@host1:port1,host2:port2,.../db/service_name + """ + parsed_url = urlparse(url) + if parsed_url.scheme != 'redis+sentinel': + raise ValueError('Invalid Redis Sentinel URL') + sentinels = [] + for host_port in parsed_url.netloc.split('@')[-1].split(','): + host, port = host_port.rsplit(':', 1) + sentinels.append((host, int(port))) + kwargs = {} + if parsed_url.username: + kwargs['username'] = parsed_url.username + if parsed_url.password: + kwargs['password'] = parsed_url.password + service_name = None + if parsed_url.path: + parts = parsed_url.path.split('/') + if len(parts) >= 2 and parts[1] != '': + kwargs['db'] = int(parts[1]) + if len(parts) >= 3 and parts[2] != '': + service_name = parts[2] + return sentinels, service_name, kwargs + + class RedisManager(PubSubManager): # pragma: no cover """Redis based client manager. @@ -27,15 +54,18 @@ class RedisManager(PubSubManager): # pragma: no cover server = socketio.Server(client_manager=socketio.RedisManager(url)) :param url: The connection URL for the Redis server. For a default Redis - store running on the same host, use ``redis://``. To use an - SSL connection, use ``rediss://``. + store running on the same host, use ``redis://``. To use a + TLS connection, use ``rediss://``. To use Redis Sentinel, use + ``redis+sentinel://`` with a comma-separated list of hosts + and the service name after the db in the URL path. Example: + ``redis+sentinel://user:pw@host1:1234,host2:2345/0/myredis``. :param channel: The channel name on which the server sends and receives notifications. Must be the same in all the servers. :param write_only: If set to ``True``, only initialize to emit events. The default of ``False`` initializes the class for emitting and receiving. :param redis_options: additional keyword arguments to be passed to - ``Redis.from_url()``. + ``Redis.from_url()`` or ``Sentinel()``. """ name = 'redis' @@ -66,8 +96,16 @@ def initialize(self): 'with ' + self.server.async_mode) def _redis_connect(self): - self.redis = redis.Redis.from_url(self.redis_url, - **self.redis_options) + if not self.redis_url.startswith('redis+sentinel://'): + self.redis = redis.Redis.from_url(self.redis_url, + **self.redis_options) + else: + sentinels, service_name, connection_kwargs = \ + parse_redis_sentinel_url(self.redis_url) + kwargs = self.redis_options + kwargs.update(connection_kwargs) + sentinel = redis.sentinel.Sentinel(sentinels, **kwargs) + self.redis = sentinel.master_for(service_name or self.channel) self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True) def _publish(self, data): diff --git a/tests/common/test_redis_manager.py b/tests/common/test_redis_manager.py new file mode 100644 index 00000000..3e5ee1ef --- /dev/null +++ b/tests/common/test_redis_manager.py @@ -0,0 +1,38 @@ +import pytest + +from socketio.redis_manager import parse_redis_sentinel_url + + +class TestPubSubManager: + def test_sentinel_url_parser(self): + with pytest.raises(ValueError): + parse_redis_sentinel_url('redis://localhost:6379/0') + + assert parse_redis_sentinel_url( + 'redis+sentinel://localhost:6379' + ) == ( + [('localhost', 6379)], + None, + {} + ) + assert parse_redis_sentinel_url( + 'redis+sentinel://192.168.0.1:6379,192.168.0.2:6379/' + ) == ( + [('192.168.0.1', 6379), ('192.168.0.2', 6379)], + None, + {} + ) + assert parse_redis_sentinel_url( + 'redis+sentinel://h1:6379,h2:6379/0' + ) == ( + [('h1', 6379), ('h2', 6379)], + None, + {'db': 0} + ) + assert parse_redis_sentinel_url( + 'redis+sentinel://user:password@h1:6379,h2:6379,h1:6380/0/myredis' + ) == ( + [('h1', 6379), ('h2', 6379), ('h1', 6380)], + 'myredis', + {'username': 'user', 'password': 'password', 'db': 0} + ) From 5c93c59648358862514f317838f61498a101ba54 Mon Sep 17 00:00:00 2001 From: Tim Van Baak <40180944+tvanbaak@users.noreply.github.com> Date: Fri, 11 Apr 2025 08:17:23 -0700 Subject: [PATCH 109/173] Preserve exception context in Client.connect (#1450) --- src/socketio/async_client.py | 2 +- src/socketio/client.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index c7f5e86e..0fa35dc6 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -158,7 +158,7 @@ async def connect(self, url, headers={}, auth=None, transports=None, await self._handle_reconnect() if self.eio.state == 'connected': return - raise exceptions.ConnectionError(exc.args[0]) from None + raise exceptions.ConnectionError(exc.args[0]) from exc if wait: try: diff --git a/src/socketio/client.py b/src/socketio/client.py index 746d2c4c..84b1643d 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -156,7 +156,7 @@ def connect(self, url, headers={}, auth=None, transports=None, self._handle_reconnect() if self.eio.state == 'connected': return - raise exceptions.ConnectionError(exc.args[0]) from None + raise exceptions.ConnectionError(exc.args[0]) from exc if wait: while self._connect_event.wait(timeout=wait_timeout): From 8ea5e814c166fdf7064ebea15defb8589d9e7260 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 12 Apr 2025 16:36:56 +0100 Subject: [PATCH 110/173] linter fixes #nolog --- examples/client/async/latency_client.py | 1 - examples/client/sync/latency_client.py | 1 - 2 files changed, 2 deletions(-) diff --git a/examples/client/async/latency_client.py b/examples/client/async/latency_client.py index 8f39549b..57be604d 100644 --- a/examples/client/async/latency_client.py +++ b/examples/client/async/latency_client.py @@ -21,7 +21,6 @@ async def connect(): @sio.event async def pong_from_server(): - global start_timer latency = time.time() - start_timer print(f'latency is {latency * 1000:.2f} ms') await sio.sleep(1) diff --git a/examples/client/sync/latency_client.py b/examples/client/sync/latency_client.py index 240d214c..94dcec9d 100644 --- a/examples/client/sync/latency_client.py +++ b/examples/client/sync/latency_client.py @@ -19,7 +19,6 @@ def connect(): @sio.event def pong_from_server(): - global start_timer latency = time.time() - start_timer print(f'latency is {latency * 1000:.2f} ms') sio.sleep(1) From 80e77170eaf73c3b14e2a899bc8627c0f119da12 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 12 Apr 2025 16:40:43 +0100 Subject: [PATCH 111/173] Release 5.13.0 --- CHANGES.md | 8 ++++++++ pyproject.toml | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 53afe357..fd061624 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,13 @@ # python-socketio change log +**Release 5.13.0** - 2025-04-12 + +- Eliminate race conditions on disconnect [#1441](https://github.com/miguelgrinberg/python-socketio/issues/1441) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/288ebb189d799a05bbc5979a834433034ea2939f)) +- Preserve exception context in `Client.connect` and `AsyncClient.connect` [#1450](https://github.com/miguelgrinberg/python-socketio/issues/1450) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/5c93c59648358862514f317838f61498a101ba54)) (thanks **Tim Van Baak**!) +- Allow custom client subclasses to be used in SimpleClient and AsyncSimpleClient [#1432](https://github.com/miguelgrinberg/python-socketio/issues/1432) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/7605630bb236b4baf98574ca2a8f0cdba2696ef4)) +- Add support for Redis Sentinel URLs in `RedisManager` and `AsyncRedisManager` [#1448](https://github.com/miguelgrinberg/python-socketio/issues/1448) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/6a52e8b50274a7524fadcd2633eb819811a63734)) +- Remove incorrect reference to an `asyncio` installation extra in documentation [#1449](https://github.com/miguelgrinberg/python-socketio/issues/1449) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/537630b983245cc137f609c3e6247d6d68ebdea5)) + **Release 5.12.1** - 2024-12-29 - Fix admin instrumentation support of disconnect reasons [#1423](https://github.com/miguelgrinberg/python-socketio/issues/1423) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/b75fd31625cfea0d8c67d776070e4f8de99c1e45)) diff --git a/pyproject.toml b/pyproject.toml index c3c629d9..f9c64b7d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.12.2.dev0" +version = "5.13.0" authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, ] From d9ecbee93c29d86faf9b5dd8f88acea76f0b7b44 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 12 Apr 2025 16:47:01 +0100 Subject: [PATCH 112/173] Version 5.13.1.dev0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index f9c64b7d..67165258 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.13.0" +version = "5.13.1.dev0" authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, ] From 259de30d436484f8a7add9a79dca34d04ddda100 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 13 Apr 2025 12:22:53 +0100 Subject: [PATCH 113/173] Remove debugging print --- src/socketio/async_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index 0fa35dc6..0a0137cb 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -191,7 +191,6 @@ async def wait(self): if not self._reconnect_task: if self.eio.state == 'connected': # pragma: no cover # connected while sleeping above - print('oops') continue break await self._reconnect_task From 81c80cddde06dd9561687c74e5769e25613282d7 Mon Sep 17 00:00:00 2001 From: Marc Mueller <30130371+cdce8p@users.noreply.github.com> Date: Fri, 18 Apr 2025 12:46:53 +0200 Subject: [PATCH 114/173] Add SPDX license identifier (#1453) --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 67165258..e7b6f65c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,7 @@ [project] name = "python-socketio" version = "5.13.1.dev0" +license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, ] @@ -9,7 +10,6 @@ classifiers = [ "Environment :: Web Environment", "Intended Audience :: Developers", "Programming Language :: Python :: 3", - "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] requires-python = ">=3.8" From 059fd90761c831e9b4a5b447ba621a6d011b3e39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 19:04:01 +0100 Subject: [PATCH 115/173] Bump h11 from 0.11.0 to 0.16.0 in /examples/server/asgi (#1458) #nolog Bumps [h11](https://github.com/python-hyper/h11) from 0.11.0 to 0.16.0. - [Commits](https://github.com/python-hyper/h11/compare/v0.11.0...v0.16.0) --- updated-dependencies: - dependency-name: h11 dependency-version: 0.16.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/asgi/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/asgi/requirements.txt b/examples/server/asgi/requirements.txt index 6dc530bd..2faa1466 100644 --- a/examples/server/asgi/requirements.txt +++ b/examples/server/asgi/requirements.txt @@ -1,5 +1,5 @@ Click==7.1.2 -h11==0.11.0 +h11==0.16.0 httptools==0.1.1 python-engineio python_socketio From 96073f4022a805378df880dbe3007a7744b60649 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 19:12:44 +0100 Subject: [PATCH 116/173] Bump h11 from 0.14.0 to 0.16.0 in /examples/server/wsgi/django_socketio (#1459) #nolog Bumps [h11](https://github.com/python-hyper/h11) from 0.14.0 to 0.16.0. - [Commits](https://github.com/python-hyper/h11/compare/v0.14.0...v0.16.0) --- updated-dependencies: - dependency-name: h11 dependency-version: 0.16.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/django_socketio/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/django_socketio/requirements.txt b/examples/server/wsgi/django_socketio/requirements.txt index 8f6c19dd..3789a68a 100644 --- a/examples/server/wsgi/django_socketio/requirements.txt +++ b/examples/server/wsgi/django_socketio/requirements.txt @@ -2,7 +2,7 @@ asgiref==3.6.0 bidict==0.22.1 Django==4.2.20 gunicorn==23.0.0 -h11==0.14.0 +h11==0.16.0 python-engineio python-socketio simple-websocket From 86a7176a5a93365fbc314880b5989159564971b3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 8 May 2025 19:06:32 +0100 Subject: [PATCH 117/173] Bump django in /examples/server/wsgi/django_socketio (#1461) #nolog Bumps [django](https://github.com/django/django) from 4.2.20 to 4.2.21. - [Commits](https://github.com/django/django/compare/4.2.20...4.2.21) --- updated-dependencies: - dependency-name: django dependency-version: 4.2.21 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/django_socketio/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/django_socketio/requirements.txt b/examples/server/wsgi/django_socketio/requirements.txt index 3789a68a..04f61d7e 100644 --- a/examples/server/wsgi/django_socketio/requirements.txt +++ b/examples/server/wsgi/django_socketio/requirements.txt @@ -1,6 +1,6 @@ asgiref==3.6.0 bidict==0.22.1 -Django==4.2.20 +Django==4.2.21 gunicorn==23.0.0 h11==0.16.0 python-engineio From 5e04003dad0140fb1c6acff328e4215e62fbc58a Mon Sep 17 00:00:00 2001 From: Func Date: Fri, 16 May 2025 16:57:34 +0800 Subject: [PATCH 118/173] Add missing `async` on session examples for the async server (#1465) --- src/socketio/async_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/socketio/async_server.py b/src/socketio/async_server.py index f10fb8ae..fac0f2b0 100644 --- a/src/socketio/async_server.py +++ b/src/socketio/async_server.py @@ -373,15 +373,15 @@ def session(self, sid, namespace=None): context manager block are saved back to the session. Example usage:: @eio.on('connect') - def on_connect(sid, environ): + async def on_connect(sid, environ): username = authenticate_user(environ) if not username: return False - with eio.session(sid) as session: + async with eio.session(sid) as session: session['username'] = username @eio.on('message') - def on_message(sid, msg): + async def on_message(sid, msg): async with eio.session(sid) as session: print('received message from ', session['username']) """ From 473e05c206f3a69874e7e073be083a92fb812fa1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 24 May 2025 14:18:40 +0100 Subject: [PATCH 119/173] Bump tornado from 6.4.2 to 6.5.1 in /examples/server/tornado (#1467) #nolog Bumps [tornado](https://github.com/tornadoweb/tornado) from 6.4.2 to 6.5.1. - [Changelog](https://github.com/tornadoweb/tornado/blob/master/docs/releases.rst) - [Commits](https://github.com/tornadoweb/tornado/compare/v6.4.2...v6.5.1) --- updated-dependencies: - dependency-name: tornado dependency-version: 6.5.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/tornado/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/tornado/requirements.txt b/examples/server/tornado/requirements.txt index 32346eff..4e2915c4 100644 --- a/examples/server/tornado/requirements.txt +++ b/examples/server/tornado/requirements.txt @@ -1,4 +1,4 @@ -tornado==6.4.2 +tornado==6.5.1 python-engineio python_socketio six==1.10.0 From d3a9b82d5816a2f13413af769c05193ecd6d2422 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 7 Jun 2025 00:47:57 +0100 Subject: [PATCH 120/173] Bump django in /examples/server/wsgi/django_socketio (#1470) #nolog Bumps [django](https://github.com/django/django) from 4.2.21 to 4.2.22. - [Commits](https://github.com/django/django/compare/4.2.21...4.2.22) --- updated-dependencies: - dependency-name: django dependency-version: 4.2.22 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/django_socketio/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/django_socketio/requirements.txt b/examples/server/wsgi/django_socketio/requirements.txt index 04f61d7e..39eae80f 100644 --- a/examples/server/wsgi/django_socketio/requirements.txt +++ b/examples/server/wsgi/django_socketio/requirements.txt @@ -1,6 +1,6 @@ asgiref==3.6.0 bidict==0.22.1 -Django==4.2.21 +Django==4.2.22 gunicorn==23.0.0 h11==0.16.0 python-engineio From 3a002e69d9264095f9891343a7492dab9291e629 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Jul 2025 15:05:40 +0100 Subject: [PATCH 121/173] Bump aiohttp from 3.10.11 to 3.12.14 in /examples/server/aiohttp (#1474) #nolog Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.11 to 3.12.14. - [Release notes](https://github.com/aio-libs/aiohttp/releases) - [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst) - [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.11...v3.12.14) --- updated-dependencies: - dependency-name: aiohttp dependency-version: 3.12.14 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/aiohttp/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/aiohttp/requirements.txt b/examples/server/aiohttp/requirements.txt index 2bda3cff..aec8bf98 100644 --- a/examples/server/aiohttp/requirements.txt +++ b/examples/server/aiohttp/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.10.11 +aiohttp==3.12.14 async-timeout==1.1.0 chardet==2.3.0 multidict==2.1.4 From efd1247ed9ed61a61d6840dc2c8c92ab02031afb Mon Sep 17 00:00:00 2001 From: Eugnee <77396838+Eugnee@users.noreply.github.com> Date: Tue, 22 Jul 2025 20:44:15 +0300 Subject: [PATCH 122/173] channel was not properly initialized in several pubsub client managers (#1476) --- src/socketio/async_aiopika_manager.py | 2 +- src/socketio/async_redis_manager.py | 2 +- src/socketio/redis_manager.py | 2 +- src/socketio/zmq_manager.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/socketio/async_aiopika_manager.py b/src/socketio/async_aiopika_manager.py index b6f09b8b..003b67bc 100644 --- a/src/socketio/async_aiopika_manager.py +++ b/src/socketio/async_aiopika_manager.py @@ -43,12 +43,12 @@ def __init__(self, url='amqp://guest:guest@localhost:5672//', raise RuntimeError('aio_pika package is not installed ' '(Run "pip install aio_pika" in your ' 'virtualenv).') + super().__init__(channel=channel, write_only=write_only, logger=logger) self.url = url self._lock = asyncio.Lock() self.publisher_connection = None self.publisher_channel = None self.publisher_exchange = None - super().__init__(channel=channel, write_only=write_only, logger=logger) async def _connection(self): return await aio_pika.connect_robust(self.url) diff --git a/src/socketio/async_redis_manager.py b/src/socketio/async_redis_manager.py index cc82f4a5..92109a21 100644 --- a/src/socketio/async_redis_manager.py +++ b/src/socketio/async_redis_manager.py @@ -52,10 +52,10 @@ def __init__(self, url='redis://localhost:6379/0', channel='socketio', '(Run "pip install redis" in your virtualenv).') if not hasattr(aioredis.Redis, 'from_url'): raise RuntimeError('Version 2 of aioredis package is required.') + super().__init__(channel=channel, write_only=write_only, logger=logger) self.redis_url = url self.redis_options = redis_options or {} self._redis_connect() - super().__init__(channel=channel, write_only=write_only, logger=logger) def _redis_connect(self): if not self.redis_url.startswith('redis+sentinel://'): diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index df98618c..c4407dfe 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -75,10 +75,10 @@ def __init__(self, url='redis://localhost:6379/0', channel='socketio', raise RuntimeError('Redis package is not installed ' '(Run "pip install redis" in your ' 'virtualenv).') + super().__init__(channel=channel, write_only=write_only, logger=logger) self.redis_url = url self.redis_options = redis_options or {} self._redis_connect() - super().__init__(channel=channel, write_only=write_only, logger=logger) def initialize(self): super().initialize() diff --git a/src/socketio/zmq_manager.py b/src/socketio/zmq_manager.py index 468dc268..aa5a49a2 100644 --- a/src/socketio/zmq_manager.py +++ b/src/socketio/zmq_manager.py @@ -57,6 +57,7 @@ def __init__(self, url='zmq+tcp://localhost:5555+5556', if not (url.startswith('zmq+tcp://') and r.search(url)): raise RuntimeError('unexpected connection string: ' + url) + super().__init__(channel=channel, write_only=write_only, logger=logger) url = url.replace('zmq+', '') (sink_url, sub_port) = url.split('+') sink_port = sink_url.split(':')[-1] @@ -72,7 +73,6 @@ def __init__(self, url='zmq+tcp://localhost:5555+5556', self.sink = sink self.sub = sub self.channel = channel - super().__init__(channel=channel, write_only=write_only, logger=logger) def _publish(self, data): pickled_data = pickle.dumps( From b01b197df1ea5914fdaa2e12758e916ebd60162a Mon Sep 17 00:00:00 2001 From: Eugnee <77396838+Eugnee@users.noreply.github.com> Date: Sun, 24 Aug 2025 13:44:50 +0300 Subject: [PATCH 123/173] redis manager, verbose error logging (#1479) --- src/socketio/async_redis_manager.py | 20 +++++++++++++------- src/socketio/redis_manager.py | 17 ++++++++++++----- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/socketio/async_redis_manager.py b/src/socketio/async_redis_manager.py index 92109a21..41ce2cea 100644 --- a/src/socketio/async_redis_manager.py +++ b/src/socketio/async_redis_manager.py @@ -78,14 +78,19 @@ async def _publish(self, data): self._redis_connect() return await self.redis.publish( self.channel, pickle.dumps(data)) - except RedisError: + except RedisError as exc: if retry: - self._get_logger().error('Cannot publish to redis... ' - 'retrying') + self._get_logger().error( + 'Cannot publish to redis... ' + 'retrying', + extra={"redis_exception": str(exc)}) retry = False else: - self._get_logger().error('Cannot publish to redis... ' - 'giving up') + self._get_logger().error( + 'Cannot publish to redis... ' + 'giving up', + extra={"redis_exception": str(exc)}) + break async def _redis_listen_with_retries(self): @@ -99,10 +104,11 @@ async def _redis_listen_with_retries(self): retry_sleep = 1 async for message in self.pubsub.listen(): yield message - except RedisError: + except RedisError as exc: self._get_logger().error('Cannot receive from redis... ' 'retrying in ' - '{} secs'.format(retry_sleep)) + '{} secs'.format(retry_sleep), + extra={"redis_exception": str(exc)}) connect = True await asyncio.sleep(retry_sleep) retry_sleep *= 2 diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index c4407dfe..73758fce 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -115,12 +115,18 @@ def _publish(self, data): if not retry: self._redis_connect() return self.redis.publish(self.channel, pickle.dumps(data)) - except redis.exceptions.RedisError: + except redis.exceptions.RedisError as exc: if retry: - logger.error('Cannot publish to redis... retrying') + logger.error( + 'Cannot publish to redis... retrying', + extra={"redis_exception": str(exc)} + ) retry = False else: - logger.error('Cannot publish to redis... giving up') + logger.error( + 'Cannot publish to redis... giving up', + extra={"redis_exception": str(exc)} + ) break def _redis_listen_with_retries(self): @@ -133,9 +139,10 @@ def _redis_listen_with_retries(self): self.pubsub.subscribe(self.channel) retry_sleep = 1 yield from self.pubsub.listen() - except redis.exceptions.RedisError: + except redis.exceptions.RedisError as exc: logger.error('Cannot receive from redis... ' - 'retrying in {} secs'.format(retry_sleep)) + 'retrying in {} secs'.format(retry_sleep), + extra={"redis_exception": str(exc)}) connect = True time.sleep(retry_sleep) retry_sleep *= 2 From 3625fe821debf33e5430bff6375ec85056a5d95f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 30 Aug 2025 10:51:56 +0100 Subject: [PATCH 124/173] Bump eventlet from 0.35.2 to 0.40.3 in /examples/server/wsgi (#1491) #nolog Bumps [eventlet](https://github.com/eventlet/eventlet) from 0.35.2 to 0.40.3. - [Changelog](https://github.com/eventlet/eventlet/blob/master/NEWS) - [Commits](https://github.com/eventlet/eventlet/compare/v0.35.2...0.40.3) --- updated-dependencies: - dependency-name: eventlet dependency-version: 0.40.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/requirements.txt b/examples/server/wsgi/requirements.txt index 3026d7f9..f4b71137 100644 --- a/examples/server/wsgi/requirements.txt +++ b/examples/server/wsgi/requirements.txt @@ -1,7 +1,7 @@ Click==7.0 enum-compat==0.0.2 enum34==1.1.6 -eventlet==0.35.2 +eventlet==0.40.3 Flask==1.0.2 greenlet==0.4.12 itsdangerous==1.1.0 From b3da354ed9eb46c0fb847c628b379ccae475a970 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 24 Sep 2025 20:28:55 +0100 Subject: [PATCH 125/173] Add message queue deployment recommendations --- docs/server.rst | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/server.rst b/docs/server.rst index 9432a172..508bd46f 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -1089,6 +1089,25 @@ The RabbitMQ queue is configured through the mgr = socketio.AsyncAioPikaManager('amqp://') sio = socketio.AsyncServer(client_manager=mgr) +Deploying the Message Queue for Production +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For a production deployment there are a few recommendations to keep your +application secure. + +First of all, the message queue should never be listening on a public network +interface, to ensure that external clients never connect to it. The use of a +private network (VPC), where the communication between servers can happen +privately is highly recommended. + +In addition, all message queues support authentication and encryption. +Authentication ensures that only the Socket.IO servers and related processes +have access, while encryption prevents data to be collected by a third-party +listening on the network. + +Access credentials can be included in the connection URLs that are passed to the +client managers. + Horizontal Scaling ~~~~~~~~~~~~~~~~~~ From 5dc2aea077469ad318e47b28a84845c5efb6bdcf Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 28 Sep 2025 10:16:15 +0100 Subject: [PATCH 126/173] keep track of which namespaces failed to connect (#1496) --- src/socketio/async_client.py | 8 ++++++-- src/socketio/base_client.py | 1 + src/socketio/client.py | 10 +++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index 0a0137cb..d84988a6 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -139,6 +139,7 @@ async def connect(self, url, headers={}, auth=None, transports=None, namespaces = [namespaces] self.connection_namespaces = namespaces self.namespaces = {} + self.failed_namespaces = [] if self._connect_event is None: self._connect_event = self.eio.create_event() else: @@ -166,14 +167,16 @@ async def connect(self, url, headers={}, auth=None, transports=None, await asyncio.wait_for(self._connect_event.wait(), wait_timeout) self._connect_event.clear() - if set(self.namespaces) == set(self.connection_namespaces): + if len(self.namespaces) + len(self.failed_namespaces) == \ + len(self.connection_namespaces): break except asyncio.TimeoutError: pass if set(self.namespaces) != set(self.connection_namespaces): await self.disconnect() raise exceptions.ConnectionError( - 'One or more namespaces failed to connect') + 'One or more namespaces failed to connect' + ', '.join(self.failed_namespaces)) self.connected = True @@ -448,6 +451,7 @@ async def _handle_error(self, namespace, data): elif not isinstance(data, (tuple, list)): data = (data,) await self._trigger_event('connect_error', namespace, *data) + self.failed_namespaces.append(namespace) self._connect_event.set() if namespace in self.namespaces: del self.namespaces[namespace] diff --git a/src/socketio/base_client.py b/src/socketio/base_client.py index 7bf44207..0232dca7 100644 --- a/src/socketio/base_client.py +++ b/src/socketio/base_client.py @@ -93,6 +93,7 @@ def __init__(self, reconnection=True, reconnection_attempts=0, self.connected = False #: Indicates if the client is connected or not. self.namespaces = {} #: set of connected namespaces. + self.failed_namespaces = [] self.handlers = {} self.namespace_handlers = {} self.callbacks = {} diff --git a/src/socketio/client.py b/src/socketio/client.py index 84b1643d..1935821d 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -137,6 +137,7 @@ def connect(self, url, headers={}, auth=None, transports=None, namespaces = [namespaces] self.connection_namespaces = namespaces self.namespaces = {} + self.failed_namespaces = [] if self._connect_event is None: self._connect_event = self.eio.create_event() else: @@ -161,12 +162,14 @@ def connect(self, url, headers={}, auth=None, transports=None, if wait: while self._connect_event.wait(timeout=wait_timeout): self._connect_event.clear() - if set(self.namespaces) == set(self.connection_namespaces): + if len(self.namespaces) + len(self.failed_namespaces) == \ + len(self.connection_namespaces): break if set(self.namespaces) != set(self.connection_namespaces): self.disconnect() raise exceptions.ConnectionError( - 'One or more namespaces failed to connect') + 'One or more namespaces failed to connect: ' + ', '.join(self.failed_namespaces)) self.connected = True @@ -425,6 +428,7 @@ def _handle_error(self, namespace, data): elif not isinstance(data, (tuple, list)): data = (data,) self._trigger_event('connect_error', namespace, *data) + self.failed_namespaces.append(namespace) self._connect_event.set() if namespace in self.namespaces: del self.namespaces[namespace] @@ -439,7 +443,7 @@ def _trigger_event(self, event, namespace, *args): if handler: try: return handler(*args) - except TypeError: + except TypeError: # pragma: no cover # the legacy disconnect event does not take a reason argument if event == 'disconnect': return handler(*args[:-1]) From 36a89226a2fb18f876dcba48125a8c51904586ec Mon Sep 17 00:00:00 2001 From: phi-friday Date: Sun, 28 Sep 2025 18:17:55 +0900 Subject: [PATCH 127/173] Add support for valkey in the Redis client managers (#1488) --- src/socketio/async_redis_manager.py | 54 +++++++++++++++++++++------ src/socketio/redis_manager.py | 58 ++++++++++++++++++++++------- tests/common/test_redis_manager.py | 14 ++++--- 3 files changed, 96 insertions(+), 30 deletions(-) diff --git a/src/socketio/async_redis_manager.py b/src/socketio/async_redis_manager.py index 41ce2cea..b37e9059 100644 --- a/src/socketio/async_redis_manager.py +++ b/src/socketio/async_redis_manager.py @@ -1,5 +1,6 @@ import asyncio import pickle +from urllib.parse import urlparse try: # pragma: no cover from redis import asyncio as aioredis @@ -12,6 +13,13 @@ aioredis = None RedisError = None +try: # pragma: no cover + from valkey import asyncio as valkey + from valkey.exceptions import ValkeyError +except ImportError: # pragma: no cover + valkey = None + ValkeyError = None + from .async_pubsub_manager import AsyncPubSubManager from .redis_manager import parse_redis_sentinel_url @@ -47,38 +55,61 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover def __init__(self, url='redis://localhost:6379/0', channel='socketio', write_only=False, logger=None, redis_options=None): - if aioredis is None: + if aioredis is None and valkey is None: raise RuntimeError('Redis package is not installed ' - '(Run "pip install redis" in your virtualenv).') - if not hasattr(aioredis.Redis, 'from_url'): + '(Run "pip install redis" or ' + '"pip install valkey" ' + 'in your virtualenv).') + if aioredis and not hasattr(aioredis.Redis, 'from_url'): raise RuntimeError('Version 2 of aioredis package is required.') super().__init__(channel=channel, write_only=write_only, logger=logger) self.redis_url = url self.redis_options = redis_options or {} self._redis_connect() + def _get_redis_module_and_error(self): + parsed_url = urlparse(self.redis_url) + schema = parsed_url.scheme.split('+', 1)[0].lower() + if schema == 'redis': + if aioredis is None or RedisError is None: + raise RuntimeError('Redis package is not installed ' + '(Run "pip install redis" ' + 'in your virtualenv).') + return aioredis, RedisError + if schema == 'valkey': + if valkey is None or ValkeyError is None: + raise RuntimeError('Valkey package is not installed ' + '(Run "pip install valkey" ' + 'in your virtualenv).') + return valkey, ValkeyError + error_msg = f'Unsupported Redis URL schema: {schema}' + raise ValueError(error_msg) + def _redis_connect(self): - if not self.redis_url.startswith('redis+sentinel://'): - self.redis = aioredis.Redis.from_url(self.redis_url, - **self.redis_options) - else: + module, _ = self._get_redis_module_and_error() + parsed_url = urlparse(self.redis_url) + if parsed_url.scheme in {"redis+sentinel", "valkey+sentinel"}: sentinels, service_name, connection_kwargs = \ parse_redis_sentinel_url(self.redis_url) kwargs = self.redis_options kwargs.update(connection_kwargs) - sentinel = aioredis.sentinel.Sentinel(sentinels, **kwargs) + sentinel = module.sentinel.Sentinel(sentinels, **kwargs) self.redis = sentinel.master_for(service_name or self.channel) + else: + self.redis = module.Redis.from_url(self.redis_url, + **self.redis_options) self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True) async def _publish(self, data): retry = True + _, error = self._get_redis_module_and_error() while True: try: if not retry: self._redis_connect() return await self.redis.publish( self.channel, pickle.dumps(data)) - except RedisError as exc: + except error as exc: if retry: self._get_logger().error( 'Cannot publish to redis... ' @@ -96,6 +127,7 @@ async def _publish(self, data): async def _redis_listen_with_retries(self): retry_sleep = 1 connect = False + _, error = self._get_redis_module_and_error() while True: try: if connect: @@ -104,10 +136,10 @@ async def _redis_listen_with_retries(self): retry_sleep = 1 async for message in self.pubsub.listen(): yield message - except RedisError as exc: + except error as exc: self._get_logger().error('Cannot receive from redis... ' 'retrying in ' - '{} secs'.format(retry_sleep), + f'{retry_sleep} secs', extra={"redis_exception": str(exc)}) connect = True await asyncio.sleep(retry_sleep) diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index 73758fce..2e68c31c 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -3,10 +3,19 @@ import time from urllib.parse import urlparse -try: +try: # pragma: no cover import redis + from redis.exceptions import RedisError except ImportError: redis = None + RedisError = None + +try: # pragma: no cover + import valkey + from valkey.exceptions import ValkeyError +except ImportError: + valkey = None + ValkeyError = None from .pubsub_manager import PubSubManager @@ -18,7 +27,7 @@ def parse_redis_sentinel_url(url): redis+sentinel://[:password]@host1:port1,host2:port2,.../db/service_name """ parsed_url = urlparse(url) - if parsed_url.scheme != 'redis+sentinel': + if parsed_url.scheme not in {'redis+sentinel', 'valkey+sentinel'}: raise ValueError('Invalid Redis Sentinel URL') sentinels = [] for host_port in parsed_url.netloc.split('@')[-1].split(','): @@ -71,10 +80,11 @@ class RedisManager(PubSubManager): # pragma: no cover def __init__(self, url='redis://localhost:6379/0', channel='socketio', write_only=False, logger=None, redis_options=None): - if redis is None: + if redis is None and valkey is None: raise RuntimeError('Redis package is not installed ' - '(Run "pip install redis" in your ' - 'virtualenv).') + '(Run "pip install redis" ' + 'or "pip install valkey" ' + 'in your virtualenv).') super().__init__(channel=channel, write_only=write_only, logger=logger) self.redis_url = url self.redis_options = redis_options or {} @@ -95,27 +105,48 @@ def initialize(self): 'Redis requires a monkey patched socket library to work ' 'with ' + self.server.async_mode) + def _get_redis_module_and_error(self): + parsed_url = urlparse(self.redis_url) + schema = parsed_url.scheme.split('+', 1)[0].lower() + if schema == 'redis': + if redis is None or RedisError is None: + raise RuntimeError('Redis package is not installed ' + '(Run "pip install redis" ' + 'in your virtualenv).') + return redis, RedisError + if schema == 'valkey': + if valkey is None or ValkeyError is None: + raise RuntimeError('Valkey package is not installed ' + '(Run "pip install valkey" ' + 'in your virtualenv).') + return valkey, ValkeyError + error_msg = f'Unsupported Redis URL schema: {schema}' + raise ValueError(error_msg) + def _redis_connect(self): - if not self.redis_url.startswith('redis+sentinel://'): - self.redis = redis.Redis.from_url(self.redis_url, - **self.redis_options) - else: + module, _ = self._get_redis_module_and_error() + parsed_url = urlparse(self.redis_url) + if parsed_url.scheme in {"redis+sentinel", "valkey+sentinel"}: sentinels, service_name, connection_kwargs = \ parse_redis_sentinel_url(self.redis_url) kwargs = self.redis_options kwargs.update(connection_kwargs) - sentinel = redis.sentinel.Sentinel(sentinels, **kwargs) + sentinel = module.sentinel.Sentinel(sentinels, **kwargs) self.redis = sentinel.master_for(service_name or self.channel) + else: + self.redis = module.Redis.from_url(self.redis_url, + **self.redis_options) self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True) def _publish(self, data): retry = True + _, error = self._get_redis_module_and_error() while True: try: if not retry: self._redis_connect() return self.redis.publish(self.channel, pickle.dumps(data)) - except redis.exceptions.RedisError as exc: + except error as exc: if retry: logger.error( 'Cannot publish to redis... retrying', @@ -132,6 +163,7 @@ def _publish(self, data): def _redis_listen_with_retries(self): retry_sleep = 1 connect = False + _, error = self._get_redis_module_and_error() while True: try: if connect: @@ -139,9 +171,9 @@ def _redis_listen_with_retries(self): self.pubsub.subscribe(self.channel) retry_sleep = 1 yield from self.pubsub.listen() - except redis.exceptions.RedisError as exc: + except error as exc: logger.error('Cannot receive from redis... ' - 'retrying in {} secs'.format(retry_sleep), + f'retrying in {retry_sleep} secs', extra={"redis_exception": str(exc)}) connect = True time.sleep(retry_sleep) diff --git a/tests/common/test_redis_manager.py b/tests/common/test_redis_manager.py index 3e5ee1ef..48dfb4a9 100644 --- a/tests/common/test_redis_manager.py +++ b/tests/common/test_redis_manager.py @@ -4,33 +4,35 @@ class TestPubSubManager: - def test_sentinel_url_parser(self): + @pytest.mark.parametrize('rtype', ['redis', 'valkey']) + def test_sentinel_url_parser(self, rtype): with pytest.raises(ValueError): - parse_redis_sentinel_url('redis://localhost:6379/0') + parse_redis_sentinel_url(f'{rtype}://localhost:6379/0') assert parse_redis_sentinel_url( - 'redis+sentinel://localhost:6379' + f'{rtype}+sentinel://localhost:6379' ) == ( [('localhost', 6379)], None, {} ) assert parse_redis_sentinel_url( - 'redis+sentinel://192.168.0.1:6379,192.168.0.2:6379/' + f'{rtype}+sentinel://192.168.0.1:6379,192.168.0.2:6379/' ) == ( [('192.168.0.1', 6379), ('192.168.0.2', 6379)], None, {} ) assert parse_redis_sentinel_url( - 'redis+sentinel://h1:6379,h2:6379/0' + f'{rtype}+sentinel://h1:6379,h2:6379/0' ) == ( [('h1', 6379), ('h2', 6379)], None, {'db': 0} ) assert parse_redis_sentinel_url( - 'redis+sentinel://user:password@h1:6379,h2:6379,h1:6380/0/myredis' + f'{rtype}+sentinel://' + 'user:password@h1:6379,h2:6379,h1:6380/0/myredis' ) == ( [('h1', 6379), ('h2', 6379), ('h1', 6380)], 'myredis', From e59acf146550d5c07f530a4766dfdfffda21e20c Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 28 Sep 2025 22:06:51 +0100 Subject: [PATCH 128/173] Address failures of test suite on Mac (#1497) --- tests/async/test_admin.py | 48 +++++++++++++++++++------------------- tests/common/test_admin.py | 42 ++++++++++++++++----------------- tests/web_server.py | 2 +- 3 files changed, 46 insertions(+), 46 deletions(-) diff --git a/tests/async/test_admin.py b/tests/async/test_admin.py index a1cf97c4..62806cbb 100644 --- a/tests/async/test_admin.py +++ b/tests/async/test_admin.py @@ -107,23 +107,23 @@ def test_missing_auth(self): @with_instrumented_server(auth=False) def test_admin_connect_with_no_auth(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) @with_instrumented_server(auth={'foo': 'bar'}) def test_admin_connect_with_dict_auth(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect( 'http://localhost:8900', namespace='/admin', auth={'foo': 'baz'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect( 'http://localhost:8900', namespace='/admin') @@ -131,52 +131,52 @@ def test_admin_connect_with_dict_auth(self): @with_instrumented_server(auth=[{'foo': 'bar'}, {'u': 'admin', 'p': 'secret'}]) def test_admin_connect_with_list_auth(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'u': 'admin', 'p': 'secret'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'baz'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect('http://localhost:8900', namespace='/admin') @with_instrumented_server(auth=_custom_auth) def test_admin_connect_with_function_auth(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'baz'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect('http://localhost:8900', namespace='/admin') @with_instrumented_server(auth=_async_custom_auth) def test_admin_connect_with_async_function_auth(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'baz'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect('http://localhost:8900', namespace='/admin') @with_instrumented_server() def test_admin_connect_only_admin(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') sid = admin_client.sid events = self._expect({'config': 1, 'all_sockets': 1, @@ -201,10 +201,10 @@ def test_admin_connect_only_admin(self): @with_instrumented_server() def test_admin_connect_with_others(self): - with socketio.SimpleClient() as client1, \ - socketio.SimpleClient() as client2, \ - socketio.SimpleClient() as client3, \ - socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as client1, \ + socketio.SimpleClient(reconnection=False) as client2, \ + socketio.SimpleClient(reconnection=False) as client3, \ + socketio.SimpleClient(reconnection=False) as admin_client: client1.connect('http://localhost:8900') client1.emit('enter_room', 'room') sid1 = client1.sid @@ -251,7 +251,7 @@ def test_admin_connect_with_others(self): @with_instrumented_server(mode='production', read_only=True) def test_admin_connect_production(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') events = self._expect({'config': 1, 'server_stats': 2}, admin_client) @@ -272,9 +272,9 @@ def test_admin_connect_production(self): @with_instrumented_server() def test_admin_features(self): - with socketio.SimpleClient() as client1, \ - socketio.SimpleClient() as client2, \ - socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as client1, \ + socketio.SimpleClient(reconnection=False) as client2, \ + socketio.SimpleClient(reconnection=False) as admin_client: client1.connect('http://localhost:8900') client2.connect('http://localhost:8900') admin_client.connect('http://localhost:8900', namespace='/admin') diff --git a/tests/common/test_admin.py b/tests/common/test_admin.py index e7667311..a3847898 100644 --- a/tests/common/test_admin.py +++ b/tests/common/test_admin.py @@ -97,23 +97,23 @@ def test_missing_auth(self): @with_instrumented_server(auth=False) def test_admin_connect_with_no_auth(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) @with_instrumented_server(auth={'foo': 'bar'}) def test_admin_connect_with_dict_auth(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect( 'http://localhost:8900', namespace='/admin', auth={'foo': 'baz'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect( 'http://localhost:8900', namespace='/admin') @@ -121,38 +121,38 @@ def test_admin_connect_with_dict_auth(self): @with_instrumented_server(auth=[{'foo': 'bar'}, {'u': 'admin', 'p': 'secret'}]) def test_admin_connect_with_list_auth(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'u': 'admin', 'p': 'secret'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'baz'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect('http://localhost:8900', namespace='/admin') @with_instrumented_server(auth=_custom_auth) def test_admin_connect_with_function_auth(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'bar'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect('http://localhost:8900', namespace='/admin', auth={'foo': 'baz'}) - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: with pytest.raises(ConnectionError): admin_client.connect('http://localhost:8900', namespace='/admin') @with_instrumented_server() def test_admin_connect_only_admin(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') sid = admin_client.sid events = self._expect({'config': 1, 'all_sockets': 1, @@ -177,10 +177,10 @@ def test_admin_connect_only_admin(self): @with_instrumented_server() def test_admin_connect_with_others(self): - with socketio.SimpleClient() as client1, \ - socketio.SimpleClient() as client2, \ - socketio.SimpleClient() as client3, \ - socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as client1, \ + socketio.SimpleClient(reconnection=False) as client2, \ + socketio.SimpleClient(reconnection=False) as client3, \ + socketio.SimpleClient(reconnection=False) as admin_client: client1.connect('http://localhost:8900') client1.emit('enter_room', 'room') sid1 = client1.sid @@ -227,7 +227,7 @@ def test_admin_connect_with_others(self): @with_instrumented_server(mode='production', read_only=True) def test_admin_connect_production(self): - with socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as admin_client: admin_client.connect('http://localhost:8900', namespace='/admin') events = self._expect({'config': 1, 'server_stats': 2}, admin_client) @@ -248,9 +248,9 @@ def test_admin_connect_production(self): @with_instrumented_server() def test_admin_features(self): - with socketio.SimpleClient() as client1, \ - socketio.SimpleClient() as client2, \ - socketio.SimpleClient() as admin_client: + with socketio.SimpleClient(reconnection=False) as client1, \ + socketio.SimpleClient(reconnection=False) as client2, \ + socketio.SimpleClient(reconnection=False) as admin_client: client1.connect('http://localhost:8900') client2.connect('http://localhost:8900') admin_client.connect('http://localhost:8900', namespace='/admin') diff --git a/tests/web_server.py b/tests/web_server.py index cb24668e..113d475e 100644 --- a/tests/web_server.py +++ b/tests/web_server.py @@ -47,7 +47,7 @@ def get_environ(self): env['gunicorn.socket'] = self.connection return env - self.httpd = make_server('', port, self._app_wrapper, + self.httpd = make_server('localhost', port, self._app_wrapper, ThreadingWSGIServer, WebSocketRequestHandler) self.thread = threading.Thread(target=self.httpd.serve_forever) self.thread.start() From 23556fb3dcb37074020494df40fb4495d47e7efe Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Mon, 29 Sep 2025 07:32:12 +0100 Subject: [PATCH 129/173] Fixed transport property of the simple clients to be a string as documented (Fixes #1499) --- src/socketio/async_simple_client.py | 2 +- src/socketio/simple_client.py | 2 +- tests/async/test_simple_client.py | 2 +- tests/common/test_simple_client.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/socketio/async_simple_client.py b/src/socketio/async_simple_client.py index adac6ead..61425220 100644 --- a/src/socketio/async_simple_client.py +++ b/src/socketio/async_simple_client.py @@ -105,7 +105,7 @@ def transport(self): The transport is returned as a string and can be one of ``polling`` and ``websocket``. """ - return self.client.transport if self.client else '' + return self.client.transport() if self.client else '' async def emit(self, event, data=None): """Emit an event to the server. diff --git a/src/socketio/simple_client.py b/src/socketio/simple_client.py index 3f046b4b..9bc5390c 100644 --- a/src/socketio/simple_client.py +++ b/src/socketio/simple_client.py @@ -103,7 +103,7 @@ def transport(self): The transport is returned as a string and can be one of ``polling`` and ``websocket``. """ - return self.client.transport if self.client else '' + return self.client.transport() if self.client else '' def emit(self, event, data=None): """Emit an event to the server. diff --git a/tests/async/test_simple_client.py b/tests/async/test_simple_client.py index bfe2a90f..0f3e5045 100644 --- a/tests/async/test_simple_client.py +++ b/tests/async/test_simple_client.py @@ -72,7 +72,7 @@ async def test_connect_twice(self): async def test_properties(self): client = AsyncSimpleClient() - client.client = mock.MagicMock(transport='websocket') + client.client = mock.MagicMock(transport=lambda: 'websocket') client.client.get_sid.return_value = 'sid' client.connected_event.set() client.connected = True diff --git a/tests/common/test_simple_client.py b/tests/common/test_simple_client.py index b17afbcc..6adcb382 100644 --- a/tests/common/test_simple_client.py +++ b/tests/common/test_simple_client.py @@ -64,7 +64,7 @@ def test_connect_twice(self): def test_properties(self): client = SimpleClient() - client.client = mock.MagicMock(transport='websocket') + client.client = mock.MagicMock(transport=lambda: 'websocket') client.client.get_sid.return_value = 'sid' client.connected_event.set() client.connected = True From f61e0bec3750a83de619db9b779f9d93e449eade Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Mon, 29 Sep 2025 09:11:08 +0100 Subject: [PATCH 130/173] wait for client to end background tasks on disconnect (#1500) --- src/socketio/async_client.py | 2 +- src/socketio/client.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index d84988a6..53c669bb 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -407,7 +407,7 @@ async def _handle_disconnect(self, namespace): del self.namespaces[namespace] if not self.namespaces: self.connected = False - await self.eio.disconnect(abort=True) + await self.eio.disconnect() async def _handle_event(self, namespace, id, data): namespace = namespace or '/' diff --git a/src/socketio/client.py b/src/socketio/client.py index 1935821d..4fc36f4e 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -387,7 +387,7 @@ def _handle_disconnect(self, namespace): del self.namespaces[namespace] if not self.namespaces: self.connected = False - self.eio.disconnect(abort=True) + self.eio.disconnect() def _handle_event(self, namespace, id, data): namespace = namespace or '/' From a59c6f520059eb095ab4472e5192ce3e486875d9 Mon Sep 17 00:00:00 2001 From: James Thistlewood Date: Tue, 30 Sep 2025 20:13:56 +0100 Subject: [PATCH 131/173] Fix: SimpleClient.call does not raise TimeoutError on timeout (#1501) --- src/socketio/async_simple_client.py | 2 ++ src/socketio/simple_client.py | 2 ++ tests/async/test_simple_client.py | 11 +++++++++++ tests/common/test_simple_client.py | 10 ++++++++++ 4 files changed, 25 insertions(+) diff --git a/src/socketio/async_simple_client.py b/src/socketio/async_simple_client.py index 61425220..a4d0928d 100644 --- a/src/socketio/async_simple_client.py +++ b/src/socketio/async_simple_client.py @@ -163,6 +163,8 @@ async def call(self, event, data=None, timeout=60): return await self.client.call(event, data, namespace=self.namespace, timeout=timeout) + except TimeoutError: + raise except SocketIOError: pass diff --git a/src/socketio/simple_client.py b/src/socketio/simple_client.py index 9bc5390c..7e1a8a5b 100644 --- a/src/socketio/simple_client.py +++ b/src/socketio/simple_client.py @@ -155,6 +155,8 @@ def call(self, event, data=None, timeout=60): try: return self.client.call(event, data, namespace=self.namespace, timeout=timeout) + except TimeoutError: + raise except SocketIOError: pass diff --git a/tests/async/test_simple_client.py b/tests/async/test_simple_client.py index 0f3e5045..b7f0193c 100644 --- a/tests/async/test_simple_client.py +++ b/tests/async/test_simple_client.py @@ -142,6 +142,17 @@ async def test_call_retries(self): client.client.call.assert_awaited_with('foo', 'bar', namespace='/', timeout=60) + async def test_call_timeout(self): + client = AsyncSimpleClient() + client.connected_event.set() + client.connected = True + client.client = mock.MagicMock() + client.client.call = mock.AsyncMock() + client.client.call.side_effect = TimeoutError() + + with pytest.raises(TimeoutError): + await client.call('foo', 'bar') + async def test_receive_with_input_buffer(self): client = AsyncSimpleClient() client.input_buffer = ['foo', 'bar'] diff --git a/tests/common/test_simple_client.py b/tests/common/test_simple_client.py index 6adcb382..43236fe8 100644 --- a/tests/common/test_simple_client.py +++ b/tests/common/test_simple_client.py @@ -130,6 +130,16 @@ def test_call_retries(self): client.client.call.assert_called_with('foo', 'bar', namespace='/', timeout=60) + def test_call_timeout(self): + client = SimpleClient() + client.connected_event.set() + client.connected = True + client.client = mock.MagicMock() + client.client.call.side_effect = TimeoutError() + + with pytest.raises(TimeoutError): + client.call('foo', 'bar') + def test_receive_with_input_buffer(self): client = SimpleClient() client.input_buffer = ['foo', 'bar'] From 53f6be094257ed81476b0e212c8cddd6d06ca39a Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 30 Sep 2025 20:22:45 +0100 Subject: [PATCH 132/173] Replace pickle with json (#1502) --- docs/server.rst | 22 +++++++++++----------- src/socketio/async_aiopika_manager.py | 6 +++--- src/socketio/async_pubsub_manager.py | 15 ++++----------- src/socketio/async_redis_manager.py | 4 ++-- src/socketio/kafka_manager.py | 6 +++--- src/socketio/kombu_manager.py | 4 ++-- src/socketio/pubsub_manager.py | 15 ++++----------- src/socketio/redis_manager.py | 4 ++-- src/socketio/zmq_manager.py | 10 +++++----- tests/async/test_pubsub_manager.py | 13 ++++++------- tests/common/test_pubsub_manager.py | 13 ++++++------- 11 files changed, 48 insertions(+), 64 deletions(-) diff --git a/docs/server.rst b/docs/server.rst index 508bd46f..9261a425 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -1096,17 +1096,17 @@ For a production deployment there are a few recommendations to keep your application secure. First of all, the message queue should never be listening on a public network -interface, to ensure that external clients never connect to it. The use of a -private network (VPC), where the communication between servers can happen -privately is highly recommended. - -In addition, all message queues support authentication and encryption. -Authentication ensures that only the Socket.IO servers and related processes -have access, while encryption prevents data to be collected by a third-party -listening on the network. - -Access credentials can be included in the connection URLs that are passed to the -client managers. +interface, to ensure that external clients never connect to it. For a single +node deployment, the queue should only listen on `localhost`. For a multi-node +system the use of a private network (VPC), where the communication between +servers can happen privately is highly recommended. + +In addition, all message queues support authentication and encryption, which +can strenthen the security of the deployment. Authentication ensures that only +the Socket.IO servers and related processes have access, while encryption +prevents data from being collected by a third-party that is listening on the +network. Access credentials can be included in the connection URLs that are +passed to the client managers. Horizontal Scaling ~~~~~~~~~~~~~~~~~~ diff --git a/src/socketio/async_aiopika_manager.py b/src/socketio/async_aiopika_manager.py index 003b67bc..1485d37a 100644 --- a/src/socketio/async_aiopika_manager.py +++ b/src/socketio/async_aiopika_manager.py @@ -1,6 +1,6 @@ import asyncio -import pickle +from engineio import json from .async_pubsub_manager import AsyncPubSubManager try: @@ -82,7 +82,7 @@ async def _publish(self, data): try: await self.publisher_exchange.publish( aio_pika.Message( - body=pickle.dumps(data), + body=json.dumps(data), delivery_mode=aio_pika.DeliveryMode.PERSISTENT ), routing_key='*', ) @@ -113,7 +113,7 @@ async def _listen(self): async with queue.iterator() as queue_iter: async for message in queue_iter: async with message.process(): - yield pickle.loads(message.body) + yield message.body retry_sleep = 1 except aio_pika.AMQPException: self._get_logger().error( diff --git a/src/socketio/async_pubsub_manager.py b/src/socketio/async_pubsub_manager.py index 72946eb2..9ccc3382 100644 --- a/src/socketio/async_pubsub_manager.py +++ b/src/socketio/async_pubsub_manager.py @@ -3,7 +3,6 @@ import uuid from engineio import json -import pickle from .async_manager import AsyncManager @@ -202,16 +201,10 @@ async def _thread(self): if isinstance(message, dict): data = message else: - if isinstance(message, bytes): # pragma: no cover - try: - data = pickle.loads(message) - except: - pass - if data is None: - try: - data = json.loads(message) - except: - pass + try: + data = json.loads(message) + except: + pass if data and 'method' in data: self._get_logger().debug('pubsub message: {}'.format( data['method'])) diff --git a/src/socketio/async_redis_manager.py b/src/socketio/async_redis_manager.py index b37e9059..4f9e3264 100644 --- a/src/socketio/async_redis_manager.py +++ b/src/socketio/async_redis_manager.py @@ -1,5 +1,4 @@ import asyncio -import pickle from urllib.parse import urlparse try: # pragma: no cover @@ -20,6 +19,7 @@ valkey = None ValkeyError = None +from engineio import json from .async_pubsub_manager import AsyncPubSubManager from .redis_manager import parse_redis_sentinel_url @@ -108,7 +108,7 @@ async def _publish(self, data): if not retry: self._redis_connect() return await self.redis.publish( - self.channel, pickle.dumps(data)) + self.channel, json.dumps(data)) except error as exc: if retry: self._get_logger().error( diff --git a/src/socketio/kafka_manager.py b/src/socketio/kafka_manager.py index 11b87ad8..a9f1a075 100644 --- a/src/socketio/kafka_manager.py +++ b/src/socketio/kafka_manager.py @@ -1,11 +1,11 @@ import logging -import pickle try: import kafka except ImportError: kafka = None +from engineio import json from .pubsub_manager import PubSubManager logger = logging.getLogger('socketio') @@ -53,7 +53,7 @@ def __init__(self, url='kafka://localhost:9092', channel='socketio', bootstrap_servers=self.kafka_urls) def _publish(self, data): - self.producer.send(self.channel, value=pickle.dumps(data)) + self.producer.send(self.channel, value=json.dumps(data)) self.producer.flush() def _kafka_listen(self): @@ -62,4 +62,4 @@ def _kafka_listen(self): def _listen(self): for message in self._kafka_listen(): if message.topic == self.channel: - yield pickle.loads(message.value) + yield message.value diff --git a/src/socketio/kombu_manager.py b/src/socketio/kombu_manager.py index 09e260c9..bc84bbb9 100644 --- a/src/socketio/kombu_manager.py +++ b/src/socketio/kombu_manager.py @@ -1,4 +1,3 @@ -import pickle import time import uuid @@ -7,6 +6,7 @@ except ImportError: kombu = None +from engineio import json from .pubsub_manager import PubSubManager @@ -102,7 +102,7 @@ def _publish(self, data): try: producer_publish = self._producer_publish( self.publisher_connection) - producer_publish(pickle.dumps(data)) + producer_publish(json.dumps(data)) break except (OSError, kombu.exceptions.KombuError): if retry: diff --git a/src/socketio/pubsub_manager.py b/src/socketio/pubsub_manager.py index 3270b4cb..80744f2f 100644 --- a/src/socketio/pubsub_manager.py +++ b/src/socketio/pubsub_manager.py @@ -2,7 +2,6 @@ import uuid from engineio import json -import pickle from .manager import Manager @@ -196,16 +195,10 @@ def _thread(self): if isinstance(message, dict): data = message else: - if isinstance(message, bytes): # pragma: no cover - try: - data = pickle.loads(message) - except: - pass - if data is None: - try: - data = json.loads(message) - except: - pass + try: + data = json.loads(message) + except: + pass if data and 'method' in data: self._get_logger().debug('pubsub message: {}'.format( data['method'])) diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index 2e68c31c..4f701b92 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -1,5 +1,4 @@ import logging -import pickle import time from urllib.parse import urlparse @@ -17,6 +16,7 @@ valkey = None ValkeyError = None +from engineio import json from .pubsub_manager import PubSubManager logger = logging.getLogger('socketio') @@ -145,7 +145,7 @@ def _publish(self, data): try: if not retry: self._redis_connect() - return self.redis.publish(self.channel, pickle.dumps(data)) + return self.redis.publish(self.channel, json.dumps(data)) except error as exc: if retry: logger.error( diff --git a/src/socketio/zmq_manager.py b/src/socketio/zmq_manager.py index aa5a49a2..a71b869c 100644 --- a/src/socketio/zmq_manager.py +++ b/src/socketio/zmq_manager.py @@ -1,6 +1,6 @@ -import pickle import re +from engineio import json from .pubsub_manager import PubSubManager @@ -75,14 +75,14 @@ def __init__(self, url='zmq+tcp://localhost:5555+5556', self.channel = channel def _publish(self, data): - pickled_data = pickle.dumps( + packed_data = json.dumps( { 'type': 'message', 'channel': self.channel, 'data': data } - ) - return self.sink.send(pickled_data) + ).encode() + return self.sink.send(packed_data) def zmq_listen(self): while True: @@ -94,7 +94,7 @@ def _listen(self): for message in self.zmq_listen(): if isinstance(message, bytes): try: - message = pickle.loads(message) + message = json.loads(message) except Exception: pass if isinstance(message, dict) and \ diff --git a/tests/async/test_pubsub_manager.py b/tests/async/test_pubsub_manager.py index 71d948a6..4a7012ee 100644 --- a/tests/async/test_pubsub_manager.py +++ b/tests/async/test_pubsub_manager.py @@ -1,5 +1,6 @@ import asyncio import functools +import json from unittest import mock import pytest @@ -482,22 +483,20 @@ async def test_background_thread(self): host_id = self.pm.host_id async def messages(): - import pickle - yield {'method': 'emit', 'value': 'foo', 'host_id': 'x'} yield {'missing': 'method', 'host_id': 'x'} yield '{"method": "callback", "value": "bar", "host_id": "x"}' yield {'method': 'disconnect', 'sid': '123', 'namespace': '/foo', 'host_id': 'x'} yield {'method': 'bogus', 'host_id': 'x'} - yield pickle.dumps({'method': 'close_room', 'value': 'baz', - 'host_id': 'x'}) + yield json.dumps({'method': 'close_room', 'value': 'baz', + 'host_id': 'x'}) yield {'method': 'enter_room', 'sid': '123', 'namespace': '/foo', 'room': 'room', 'host_id': 'x'} yield {'method': 'leave_room', 'sid': '123', 'namespace': '/foo', 'room': 'room', 'host_id': 'x'} yield 'bad json' - yield b'bad pickled' + yield b'bad data' # these should not publish anything on the queue, as they come from # the same host @@ -505,8 +504,8 @@ async def messages(): yield {'method': 'callback', 'value': 'bar', 'host_id': host_id} yield {'method': 'disconnect', 'sid': '123', 'namespace': '/foo', 'host_id': host_id} - yield pickle.dumps({'method': 'close_room', 'value': 'baz', - 'host_id': host_id}) + yield json.dumps({'method': 'close_room', 'value': 'baz', + 'host_id': host_id}) self.pm._listen = messages await self.pm._thread() diff --git a/tests/common/test_pubsub_manager.py b/tests/common/test_pubsub_manager.py index 6d8eda75..32c7d41d 100644 --- a/tests/common/test_pubsub_manager.py +++ b/tests/common/test_pubsub_manager.py @@ -1,4 +1,5 @@ import functools +import json import logging from unittest import mock @@ -465,22 +466,20 @@ def test_background_thread(self): host_id = self.pm.host_id def messages(): - import pickle - yield {'method': 'emit', 'value': 'foo', 'host_id': 'x'} yield {'missing': 'method', 'host_id': 'x'} yield '{"method": "callback", "value": "bar", "host_id": "x"}' yield {'method': 'disconnect', 'sid': '123', 'namespace': '/foo', 'host_id': 'x'} yield {'method': 'bogus', 'host_id': 'x'} - yield pickle.dumps({'method': 'close_room', 'value': 'baz', - 'host_id': 'x'}) + yield json.dumps({'method': 'close_room', 'value': 'baz', + 'host_id': 'x'}) yield {'method': 'enter_room', 'sid': '123', 'namespace': '/foo', 'room': 'room', 'host_id': 'x'} yield {'method': 'leave_room', 'sid': '123', 'namespace': '/foo', 'room': 'room', 'host_id': 'x'} yield 'bad json' - yield b'bad pickled' + yield b'bad data' # these should not publish anything on the queue, as they come from # the same host @@ -488,8 +487,8 @@ def messages(): yield {'method': 'callback', 'value': 'bar', 'host_id': host_id} yield {'method': 'disconnect', 'sid': '123', 'namespace': '/foo', 'host_id': host_id} - yield pickle.dumps({'method': 'close_room', 'value': 'baz', - 'host_id': host_id}) + yield json.dumps({'method': 'close_room', 'value': 'baz', + 'host_id': host_id}) self.pm._listen = mock.MagicMock(side_effect=messages) try: From 400200e00625a49796b84baa44a09cd711fabe50 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 30 Sep 2025 20:28:51 +0100 Subject: [PATCH 133/173] Release 5.14.0 --- CHANGES.md | 14 ++++++++++++++ pyproject.toml | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fd061624..9e618839 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,19 @@ # python-socketio change log +**Release 5.14.0** - 2025-09-30 + +- Replace pickle with json in message queue communications [#1502](https://github.com/miguelgrinberg/python-socketio/issues/1502) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/53f6be094257ed81476b0e212c8cddd6d06ca39a)) +- Add support for Valkey in the Redis client managers [#1488](https://github.com/miguelgrinberg/python-socketio/issues/1488) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/36a89226a2fb18f876dcba48125a8c51904586ec)) (thanks **phi-friday**!) +- Keep track of which namespaces failed to connect [#1496](https://github.com/miguelgrinberg/python-socketio/issues/1496) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/5dc2aea077469ad318e47b28a84845c5efb6bdcf)) +- Fixed transport property of the simple clients to be a string as documented [#1499](https://github.com/miguelgrinberg/python-socketio/issues/1499) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/23556fb3dcb37074020494df40fb4495d47e7efe)) +- SimpleClient.call does not raise `TimeoutError` on timeout [#1501](https://github.com/miguelgrinberg/python-socketio/issues/1501) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/a59c6f520059eb095ab4472e5192ce3e486875d9)) (thanks **James Thistlewood**!) +- Wait for client to end background tasks on disconnect [#1500](https://github.com/miguelgrinberg/python-socketio/issues/1500) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/f61e0bec3750a83de619db9b779f9d93e449eade)) +- Better error logging for the Redis managers [#1479](https://github.com/miguelgrinberg/python-socketio/issues/1479) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/b01b197df1ea5914fdaa2e12758e916ebd60162a)) (thanks **Eugnee**!) +- Channel was not properly initialized in several pubsub client managers [#1476](https://github.com/miguelgrinberg/python-socketio/issues/1476) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/efd1247ed9ed61a61d6840dc2c8c92ab02031afb)) (thanks **Eugnee**!) +- Add message queue deployment recommendations for security ([commit](https://github.com/miguelgrinberg/python-socketio/commit/b3da354ed9eb46c0fb847c628b379ccae475a970)) +- Add missing `async` on session examples for the async server [#1465](https://github.com/miguelgrinberg/python-socketio/issues/1465) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/5e04003dad0140fb1c6acff328e4215e62fbc58a)) (thanks **Func**!) +- Add SPDX license identifier [#1453](https://github.com/miguelgrinberg/python-socketio/issues/1453) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/81c80cddde06dd9561687c74e5769e25613282d7)) (thanks **Marc Mueller**!) + **Release 5.13.0** - 2025-04-12 - Eliminate race conditions on disconnect [#1441](https://github.com/miguelgrinberg/python-socketio/issues/1441) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/288ebb189d799a05bbc5979a834433034ea2939f)) diff --git a/pyproject.toml b/pyproject.toml index e7b6f65c..7b578083 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.13.1.dev0" +version = "5.14.0" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From 9e42f4650901f99d071fd5e21a68415454b571cb Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 30 Sep 2025 20:30:42 +0100 Subject: [PATCH 134/173] Version 5.14.1.dev0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7b578083..a322f165 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.14.0" +version = "5.14.1.dev0" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From a8deb3a8f3ee51d75c124157efa7fc9289fd592b Mon Sep 17 00:00:00 2001 From: Darren Chang Date: Thu, 2 Oct 2025 17:19:21 +0800 Subject: [PATCH 135/173] Add support for unix-sock protocol (#1503) --- src/socketio/async_redis_manager.py | 2 +- src/socketio/redis_manager.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/socketio/async_redis_manager.py b/src/socketio/async_redis_manager.py index 4f9e3264..b099d9eb 100644 --- a/src/socketio/async_redis_manager.py +++ b/src/socketio/async_redis_manager.py @@ -70,7 +70,7 @@ def __init__(self, url='redis://localhost:6379/0', channel='socketio', def _get_redis_module_and_error(self): parsed_url = urlparse(self.redis_url) schema = parsed_url.scheme.split('+', 1)[0].lower() - if schema == 'redis': + if schema in ['redis', 'unix']: if aioredis is None or RedisError is None: raise RuntimeError('Redis package is not installed ' '(Run "pip install redis" ' diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index 4f701b92..13d20228 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -108,7 +108,7 @@ def initialize(self): def _get_redis_module_and_error(self): parsed_url = urlparse(self.redis_url) schema = parsed_url.scheme.split('+', 1)[0].lower() - if schema == 'redis': + if schema in ['redis', 'unix']: if redis is None or RedisError is None: raise RuntimeError('Redis package is not installed ' '(Run "pip install redis" ' From 6e2d0de12bb4e4a99fdfc30bed0706ded620822c Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Thu, 2 Oct 2025 12:06:19 +0100 Subject: [PATCH 136/173] Restore support for rediss:// URLs, and add valkeys:// --- src/socketio/async_redis_manager.py | 47 ++++++------ src/socketio/redis_manager.py | 42 ++++++----- tests/async/test_redis_manager.py | 107 ++++++++++++++++++++++++++++ tests/common/test_redis_manager.py | 104 ++++++++++++++++++++++++++- tox.ini | 2 + 5 files changed, 263 insertions(+), 39 deletions(-) create mode 100644 tests/async/test_redis_manager.py diff --git a/src/socketio/async_redis_manager.py b/src/socketio/async_redis_manager.py index b099d9eb..b8ac4a0f 100644 --- a/src/socketio/async_redis_manager.py +++ b/src/socketio/async_redis_manager.py @@ -1,7 +1,7 @@ import asyncio from urllib.parse import urlparse -try: # pragma: no cover +try: from redis import asyncio as aioredis from redis.exceptions import RedisError except ImportError: # pragma: no cover @@ -12,11 +12,11 @@ aioredis = None RedisError = None -try: # pragma: no cover - from valkey import asyncio as valkey +try: + from valkey import asyncio as aiovalkey from valkey.exceptions import ValkeyError except ImportError: # pragma: no cover - valkey = None + aiovalkey = None ValkeyError = None from engineio import json @@ -24,7 +24,7 @@ from .redis_manager import parse_redis_sentinel_url -class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover +class AsyncRedisManager(AsyncPubSubManager): """Redis based client manager for asyncio servers. This class implements a Redis backend for event sharing across multiple @@ -55,12 +55,8 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover def __init__(self, url='redis://localhost:6379/0', channel='socketio', write_only=False, logger=None, redis_options=None): - if aioredis is None and valkey is None: - raise RuntimeError('Redis package is not installed ' - '(Run "pip install redis" or ' - '"pip install valkey" ' - 'in your virtualenv).') - if aioredis and not hasattr(aioredis.Redis, 'from_url'): + if aioredis and \ + not hasattr(aioredis.Redis, 'from_url'): # pragma: no cover raise RuntimeError('Version 2 of aioredis package is required.') super().__init__(channel=channel, write_only=write_only, logger=logger) self.redis_url = url @@ -69,20 +65,31 @@ def __init__(self, url='redis://localhost:6379/0', channel='socketio', def _get_redis_module_and_error(self): parsed_url = urlparse(self.redis_url) - schema = parsed_url.scheme.split('+', 1)[0].lower() - if schema in ['redis', 'unix']: + scheme = parsed_url.scheme.split('+', 1)[0].lower() + if scheme in ['redis', 'rediss']: if aioredis is None or RedisError is None: raise RuntimeError('Redis package is not installed ' '(Run "pip install redis" ' 'in your virtualenv).') return aioredis, RedisError - if schema == 'valkey': - if valkey is None or ValkeyError is None: + if scheme in ['valkey', 'valkeys']: + if aiovalkey is None or ValkeyError is None: raise RuntimeError('Valkey package is not installed ' '(Run "pip install valkey" ' 'in your virtualenv).') - return valkey, ValkeyError - error_msg = f'Unsupported Redis URL schema: {schema}' + return aiovalkey, ValkeyError + if scheme == 'unix': + if aioredis is None or RedisError is None: + if aiovalkey is None or ValkeyError is None: + raise RuntimeError('Redis package is not installed ' + '(Run "pip install redis" ' + 'or "pip install valkey" ' + 'in your virtualenv).') + else: + return aiovalkey, ValkeyError + else: + return aioredis, RedisError + error_msg = f'Unsupported Redis URL scheme: {scheme}' raise ValueError(error_msg) def _redis_connect(self): @@ -100,7 +107,7 @@ def _redis_connect(self): **self.redis_options) self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True) - async def _publish(self, data): + async def _publish(self, data): # pragma: no cover retry = True _, error = self._get_redis_module_and_error() while True: @@ -124,7 +131,7 @@ async def _publish(self, data): break - async def _redis_listen_with_retries(self): + async def _redis_listen_with_retries(self): # pragma: no cover retry_sleep = 1 connect = False _, error = self._get_redis_module_and_error() @@ -147,7 +154,7 @@ async def _redis_listen_with_retries(self): if retry_sleep > 60: retry_sleep = 60 - async def _listen(self): + async def _listen(self): # pragma: no cover channel = self.channel.encode('utf-8') await self.pubsub.subscribe(self.channel) async for message in self._redis_listen_with_retries(): diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index 13d20228..5e58ef41 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -2,17 +2,17 @@ import time from urllib.parse import urlparse -try: # pragma: no cover +try: import redis from redis.exceptions import RedisError -except ImportError: +except ImportError: # pragma: no cover redis = None RedisError = None -try: # pragma: no cover +try: import valkey from valkey.exceptions import ValkeyError -except ImportError: +except ImportError: # pragma: no cover valkey = None ValkeyError = None @@ -48,7 +48,7 @@ def parse_redis_sentinel_url(url): return sentinels, service_name, kwargs -class RedisManager(PubSubManager): # pragma: no cover +class RedisManager(PubSubManager): """Redis based client manager. This class implements a Redis backend for event sharing across multiple @@ -80,17 +80,12 @@ class RedisManager(PubSubManager): # pragma: no cover def __init__(self, url='redis://localhost:6379/0', channel='socketio', write_only=False, logger=None, redis_options=None): - if redis is None and valkey is None: - raise RuntimeError('Redis package is not installed ' - '(Run "pip install redis" ' - 'or "pip install valkey" ' - 'in your virtualenv).') super().__init__(channel=channel, write_only=write_only, logger=logger) self.redis_url = url self.redis_options = redis_options or {} self._redis_connect() - def initialize(self): + def initialize(self): # pragma: no cover super().initialize() monkey_patched = True @@ -107,20 +102,31 @@ def initialize(self): def _get_redis_module_and_error(self): parsed_url = urlparse(self.redis_url) - schema = parsed_url.scheme.split('+', 1)[0].lower() - if schema in ['redis', 'unix']: + scheme = parsed_url.scheme.split('+', 1)[0].lower() + if scheme in ['redis', 'rediss']: if redis is None or RedisError is None: raise RuntimeError('Redis package is not installed ' '(Run "pip install redis" ' 'in your virtualenv).') return redis, RedisError - if schema == 'valkey': + if scheme in ['valkey', 'valkeys']: if valkey is None or ValkeyError is None: raise RuntimeError('Valkey package is not installed ' '(Run "pip install valkey" ' 'in your virtualenv).') return valkey, ValkeyError - error_msg = f'Unsupported Redis URL schema: {schema}' + if scheme == 'unix': + if redis is None or RedisError is None: + if valkey is None or ValkeyError is None: + raise RuntimeError('Redis package is not installed ' + '(Run "pip install redis" ' + 'or "pip install valkey" ' + 'in your virtualenv).') + else: + return valkey, ValkeyError + else: + return redis, RedisError + error_msg = f'Unsupported Redis URL scheme: {scheme}' raise ValueError(error_msg) def _redis_connect(self): @@ -138,7 +144,7 @@ def _redis_connect(self): **self.redis_options) self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True) - def _publish(self, data): + def _publish(self, data): # pragma: no cover retry = True _, error = self._get_redis_module_and_error() while True: @@ -160,7 +166,7 @@ def _publish(self, data): ) break - def _redis_listen_with_retries(self): + def _redis_listen_with_retries(self): # pragma: no cover retry_sleep = 1 connect = False _, error = self._get_redis_module_and_error() @@ -181,7 +187,7 @@ def _redis_listen_with_retries(self): if retry_sleep > 60: retry_sleep = 60 - def _listen(self): + def _listen(self): # pragma: no cover channel = self.channel.encode('utf-8') self.pubsub.subscribe(self.channel) for message in self._redis_listen_with_retries(): diff --git a/tests/async/test_redis_manager.py b/tests/async/test_redis_manager.py new file mode 100644 index 00000000..01c0c375 --- /dev/null +++ b/tests/async/test_redis_manager.py @@ -0,0 +1,107 @@ +import pytest +import redis +import valkey + +from socketio import async_redis_manager +from socketio.async_redis_manager import AsyncRedisManager + + +class TestAsyncRedisManager: + def test_redis_not_installed(self): + saved_redis = async_redis_manager.aioredis + async_redis_manager.aioredis = None + + with pytest.raises(RuntimeError): + AsyncRedisManager('redis://') + assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None + + async_redis_manager.aioredis = saved_redis + + def test_valkey_not_installed(self): + saved_valkey = async_redis_manager.aiovalkey + async_redis_manager.aiovalkey = None + + with pytest.raises(RuntimeError): + AsyncRedisManager('valkey://') + assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None + + async_redis_manager.aiovalkey = saved_valkey + + def test_redis_valkey_not_installed(self): + saved_redis = async_redis_manager.aioredis + async_redis_manager.aioredis = None + saved_valkey = async_redis_manager.aiovalkey + async_redis_manager.aiovalkey = None + + with pytest.raises(RuntimeError): + AsyncRedisManager('redis://') + with pytest.raises(RuntimeError): + AsyncRedisManager('valkey://') + with pytest.raises(RuntimeError): + AsyncRedisManager('unix:///var/sock/redis.sock') + + async_redis_manager.aioredis = saved_redis + async_redis_manager.aiovalkey = saved_valkey + + def test_bad_url(self): + with pytest.raises(ValueError): + AsyncRedisManager('http://localhost:6379') + + def test_redis_connect(self): + urls = [ + 'redis://localhost:6379', + 'redis://localhost:6379/0', + 'redis://:password@localhost:6379', + 'redis://:password@localhost:6379/0', + 'redis://user:password@localhost:6379', + 'redis://user:password@localhost:6379/0', + + 'rediss://localhost:6379', + 'rediss://localhost:6379/0', + 'rediss://:password@localhost:6379', + 'rediss://:password@localhost:6379/0', + 'rediss://user:password@localhost:6379', + 'rediss://user:password@localhost:6379/0', + + 'unix:///var/sock/redis.sock', + 'unix:///var/sock/redis.sock?db=0', + 'unix://user@/var/sock/redis.sock', + 'unix://user@/var/sock/redis.sock?db=0', + + 'redis+sentinel://192.168.0.1:6379,192.168.0.2:6379/' + ] + for url in urls: + c = AsyncRedisManager(url) + assert isinstance(c.redis, redis.asyncio.Redis) + + def test_valkey_connect(self): + saved_redis = async_redis_manager.aioredis + async_redis_manager.aioredis = None + + urls = [ + 'valkey://localhost:6379', + 'valkey://localhost:6379/0', + 'valkey://:password@localhost:6379', + 'valkey://:password@localhost:6379/0', + 'valkey://user:password@localhost:6379', + 'valkey://user:password@localhost:6379/0', + + 'valkeys://localhost:6379', + 'valkeys://localhost:6379/0', + 'valkeys://:password@localhost:6379', + 'valkeys://:password@localhost:6379/0', + 'valkeys://user:password@localhost:6379', + 'valkeys://user:password@localhost:6379/0', + + 'unix:///var/sock/redis.sock', + 'unix:///var/sock/redis.sock?db=0', + 'unix://user@/var/sock/redis.sock', + 'unix://user@/var/sock/redis.sock?db=0', + + 'valkey+sentinel://192.168.0.1:6379,192.168.0.2:6379/' + ] + for url in urls: + c = AsyncRedisManager(url) + assert isinstance(c.redis, valkey.asyncio.Valkey) + + async_redis_manager.aioredis = saved_redis diff --git a/tests/common/test_redis_manager.py b/tests/common/test_redis_manager.py index 48dfb4a9..3beadf3b 100644 --- a/tests/common/test_redis_manager.py +++ b/tests/common/test_redis_manager.py @@ -1,9 +1,111 @@ import pytest +import redis +import valkey -from socketio.redis_manager import parse_redis_sentinel_url +from socketio import redis_manager +from socketio.redis_manager import RedisManager, parse_redis_sentinel_url class TestPubSubManager: + def test_redis_not_installed(self): + saved_redis = redis_manager.redis + redis_manager.redis = None + + with pytest.raises(RuntimeError): + RedisManager('redis://') + assert RedisManager('unix:///var/sock/redis.sock') is not None + + redis_manager.redis = saved_redis + + def test_valkey_not_installed(self): + saved_valkey = redis_manager.valkey + redis_manager.valkey = None + + with pytest.raises(RuntimeError): + RedisManager('valkey://') + assert RedisManager('unix:///var/sock/redis.sock') is not None + + redis_manager.valkey = saved_valkey + + def test_redis_valkey_not_installed(self): + saved_redis = redis_manager.redis + redis_manager.redis = None + saved_valkey = redis_manager.valkey + redis_manager.valkey = None + + with pytest.raises(RuntimeError): + RedisManager('redis://') + with pytest.raises(RuntimeError): + RedisManager('valkey://') + with pytest.raises(RuntimeError): + RedisManager('unix:///var/sock/redis.sock') + + redis_manager.redis = saved_redis + redis_manager.valkey = saved_valkey + + def test_bad_url(self): + with pytest.raises(ValueError): + RedisManager('http://localhost:6379') + + def test_redis_connect(self): + urls = [ + 'redis://localhost:6379', + 'redis://localhost:6379/0', + 'redis://:password@localhost:6379', + 'redis://:password@localhost:6379/0', + 'redis://user:password@localhost:6379', + 'redis://user:password@localhost:6379/0', + + 'rediss://localhost:6379', + 'rediss://localhost:6379/0', + 'rediss://:password@localhost:6379', + 'rediss://:password@localhost:6379/0', + 'rediss://user:password@localhost:6379', + 'rediss://user:password@localhost:6379/0', + + 'unix:///var/sock/redis.sock', + 'unix:///var/sock/redis.sock?db=0', + 'unix://user@/var/sock/redis.sock', + 'unix://user@/var/sock/redis.sock?db=0', + + 'redis+sentinel://192.168.0.1:6379,192.168.0.2:6379/' + ] + for url in urls: + c = RedisManager(url) + assert isinstance(c.redis, redis.Redis) + + def test_valkey_connect(self): + saved_redis = redis_manager.redis + redis_manager.redis = None + + urls = [ + 'valkey://localhost:6379', + 'valkey://localhost:6379/0', + 'valkey://:password@localhost:6379', + 'valkey://:password@localhost:6379/0', + 'valkey://user:password@localhost:6379', + 'valkey://user:password@localhost:6379/0', + + 'valkeys://localhost:6379', + 'valkeys://localhost:6379/0', + 'valkeys://:password@localhost:6379', + 'valkeys://:password@localhost:6379/0', + 'valkeys://user:password@localhost:6379', + 'valkeys://user:password@localhost:6379/0', + + 'unix:///var/sock/redis.sock', + 'unix:///var/sock/redis.sock?db=0', + 'unix://user@/var/sock/redis.sock', + 'unix://user@/var/sock/redis.sock?db=0', + + 'valkey+sentinel://192.168.0.1:6379,192.168.0.2:6379/' + ] + for url in urls: + c = RedisManager(url) + assert isinstance(c.redis, valkey.Valkey) + + redis_manager.redis = saved_redis + @pytest.mark.parametrize('rtype', ['redis', 'valkey']) def test_sentinel_url_parser(self, rtype): with pytest.raises(ValueError): diff --git a/tox.ini b/tox.ini index fc0116cb..bb830590 100644 --- a/tox.ini +++ b/tox.ini @@ -23,6 +23,8 @@ deps= websocket-client aiohttp msgpack + redis + valkey pytest pytest-asyncio pytest-timeout From 0acbf198c631fa235753651f2275b7d5133eb4a4 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Thu, 2 Oct 2025 19:41:26 +0100 Subject: [PATCH 137/173] Release 5.14.1 --- CHANGES.md | 5 +++++ pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9e618839..fe5406bb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # python-socketio change log +**Release 5.14.1** - 2025-10-02 + +- Restore support for `rediss://` URLs, and add support for `valkeys://` as well ([commit](https://github.com/miguelgrinberg/python-socketio/commit/6e2d0de12bb4e4a99fdfc30bed0706ded620822c)) +- Add support for Redis connections using unix sockets [#1503](https://github.com/miguelgrinberg/python-socketio/issues/1503) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/a8deb3a8f3ee51d75c124157efa7fc9289fd592b)) (thanks **Darren Chang**!) + **Release 5.14.0** - 2025-09-30 - Replace pickle with json in message queue communications [#1502](https://github.com/miguelgrinberg/python-socketio/issues/1502) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/53f6be094257ed81476b0e212c8cddd6d06ca39a)) diff --git a/pyproject.toml b/pyproject.toml index a322f165..a7e6aff0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.14.1.dev0" +version = "5.14.1" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From 2ffdd5535aed9f0840003a284a027f677333cd89 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Thu, 2 Oct 2025 19:44:56 +0100 Subject: [PATCH 138/173] Version 5.14.2.dev0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a7e6aff0..f3daf2dc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.14.1" +version = "5.14.2.dev0" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From f298c9b54d76ab09ff72935937e1b9575bc45ffd Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 4 Oct 2025 11:38:53 +0100 Subject: [PATCH 139/173] Fix formatting of client connection error (Fixes #1507) --- src/socketio/async_client.py | 2 +- src/socketio/client.py | 2 +- tests/async/test_client.py | 54 ++++++++++++++++++++++++++++++++++ tests/common/test_client.py | 56 ++++++++++++++++++++++++++++++++++++ 4 files changed, 112 insertions(+), 2 deletions(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index 53c669bb..a4f42671 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -175,7 +175,7 @@ async def connect(self, url, headers={}, auth=None, transports=None, if set(self.namespaces) != set(self.connection_namespaces): await self.disconnect() raise exceptions.ConnectionError( - 'One or more namespaces failed to connect' + 'One or more namespaces failed to connect: ' + ', '.join(self.failed_namespaces)) self.connected = True diff --git a/src/socketio/client.py b/src/socketio/client.py index 4fc36f4e..bf1bee45 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -168,7 +168,7 @@ def connect(self, url, headers={}, auth=None, transports=None, if set(self.namespaces) != set(self.connection_namespaces): self.disconnect() raise exceptions.ConnectionError( - 'One or more namespaces failed to connect: ' + 'One or more namespaces failed to connect: ' + ', '.join(self.failed_namespaces)) self.connected = True diff --git a/tests/async/test_client.py b/tests/async/test_client.py index d7e0f9e7..b4b0c6c5 100644 --- a/tests/async/test_client.py +++ b/tests/async/test_client.py @@ -203,6 +203,60 @@ async def mock_connect(): assert c.connected is True assert c.namespaces == {'/bar': '123', '/foo': '456'} + async def test_connect_wait_one_namespaces_error(self): + c = async_client.AsyncClient() + c.eio.connect = mock.AsyncMock() + c._connect_event = mock.MagicMock() + + async def mock_connect(): + if c.failed_namespaces == []: + c.failed_namespaces = ['/foo'] + return True + return False + + c._connect_event.wait = mock_connect + with pytest.raises(exceptions.ConnectionError, + match='failed to connect: /foo'): + await c.connect( + 'url', + namespaces=['/foo'], + wait=True, + wait_timeout=0.01, + ) + assert c.connected is False + assert c.namespaces == {} + assert c.failed_namespaces == ['/foo'] + + async def test_connect_wait_three_namespaces_error(self): + c = async_client.AsyncClient() + c.eio.connect = mock.AsyncMock() + c._connect_event = mock.MagicMock() + + async def mock_connect(): + if c.namespaces == {}: + c.namespaces = {'/bar': '123'} + return True + elif c.namespaces == {'/bar': '123'} and c.failed_namespaces == []: + c.failed_namespaces = ['/baz'] + return True + elif c.failed_namespaces == ['/baz']: + c.failed_namespaces = ['/baz', '/foo'] + return True + return False + + c._connect_event.wait = mock_connect + with pytest.raises(exceptions.ConnectionError, + match='failed to connect: /baz, /foo'): + await c.connect( + 'url', + namespaces=['/foo', '/bar', '/baz'], + wait=True, + wait_timeout=0.01, + ) + assert c.connected is False + assert c.namespaces == {'/bar': '123'} + assert c.failed_namespaces == ['/baz', '/foo'] + async def test_connect_timeout(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() diff --git a/tests/common/test_client.py b/tests/common/test_client.py index 7ee2bacf..cbda3f1f 100644 --- a/tests/common/test_client.py +++ b/tests/common/test_client.py @@ -350,6 +350,62 @@ def mock_connect(timeout): assert c.connected is True assert c.namespaces == {'/bar': '123', '/foo': '456'} + def test_connect_wait_one_namespaces_error(self): + c = client.Client() + c.eio.connect = mock.MagicMock() + c._connect_event = mock.MagicMock() + + def mock_connect(timeout): + assert timeout == 0.01 + if c.failed_namespaces == []: + c.failed_namespaces = ['/foo'] + return True + return False + + c._connect_event.wait = mock_connect + with pytest.raises(exceptions.ConnectionError, + match='failed to connect: /foo'): + c.connect( + 'url', + namespaces=['/foo'], + wait=True, + wait_timeout=0.01, + ) + assert c.connected is False + assert c.namespaces == {} + assert c.failed_namespaces == ['/foo'] + + def test_connect_wait_three_namespaces_error(self): + c = client.Client() + c.eio.connect = mock.MagicMock() + c._connect_event = mock.MagicMock() + + def mock_connect(timeout): + assert timeout == 0.01 + if c.namespaces == {}: + c.namespaces = {'/bar': '123'} + return True + elif c.namespaces == {'/bar': '123'} and c.failed_namespaces == []: + c.failed_namespaces = ['/baz'] + return True + elif c.failed_namespaces == ['/baz']: + c.failed_namespaces = ['/baz', '/foo'] + return True + return False + + c._connect_event.wait = mock_connect + with pytest.raises(exceptions.ConnectionError, + match='failed to connect: /baz, /foo'): + c.connect( + 'url', + namespaces=['/foo', '/bar', '/baz'], + wait=True, + wait_timeout=0.01, + ) + assert c.connected is False + assert c.namespaces == {'/bar': '123'} + assert c.failed_namespaces == ['/baz', '/foo'] + def test_connect_timeout(self): c = client.Client() c.eio.connect = mock.MagicMock() From 33722a0d96036f005188b07b8b46a5ef091fe65f Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 4 Oct 2025 19:21:26 +0100 Subject: [PATCH 140/173] Improve documentation of the `BaseManager.get_participants()` method --- src/socketio/base_manager.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/socketio/base_manager.py b/src/socketio/base_manager.py index dafa60ac..58706db4 100644 --- a/src/socketio/base_manager.py +++ b/src/socketio/base_manager.py @@ -29,7 +29,13 @@ def get_namespaces(self): return self.rooms.keys() def get_participants(self, namespace, room): - """Return an iterable with the active participants in a room.""" + """Return an iterable with the active participants in a room. + + Note that in a multi-server scenario this method only returns the + participants connect to the server in which the method is called. There + is currently no functionality to assemble a complete list of users across + multiple servers. + """ ns = self.rooms.get(namespace, {}) if hasattr(room, '__len__') and not isinstance(room, str): participants = ns[room[0]]._fwdm.copy() if room[0] in ns else {} From 383eeaf8a6534ee582abe26cb914c33a06daf99d Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 4 Oct 2025 19:30:23 +0100 Subject: [PATCH 141/173] linter fixes #nolog --- src/socketio/async_client.py | 4 ++-- src/socketio/base_manager.py | 4 ++-- src/socketio/client.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index a4f42671..678743a2 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -175,8 +175,8 @@ async def connect(self, url, headers={}, auth=None, transports=None, if set(self.namespaces) != set(self.connection_namespaces): await self.disconnect() raise exceptions.ConnectionError( - 'One or more namespaces failed to connect: ' + - ', '.join(self.failed_namespaces)) + 'One or more namespaces failed to connect: ' + + ', '.join(self.failed_namespaces)) self.connected = True diff --git a/src/socketio/base_manager.py b/src/socketio/base_manager.py index 58706db4..ae4530bd 100644 --- a/src/socketio/base_manager.py +++ b/src/socketio/base_manager.py @@ -33,8 +33,8 @@ def get_participants(self, namespace, room): Note that in a multi-server scenario this method only returns the participants connect to the server in which the method is called. There - is currently no functionality to assemble a complete list of users across - multiple servers. + is currently no functionality to assemble a complete list of users + across multiple servers. """ ns = self.rooms.get(namespace, {}) if hasattr(room, '__len__') and not isinstance(room, str): diff --git a/src/socketio/client.py b/src/socketio/client.py index bf1bee45..5282e0a1 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -168,8 +168,8 @@ def connect(self, url, headers={}, auth=None, transports=None, if set(self.namespaces) != set(self.connection_namespaces): self.disconnect() raise exceptions.ConnectionError( - 'One or more namespaces failed to connect: ' + - ', '.join(self.failed_namespaces)) + 'One or more namespaces failed to connect: ' + + ', '.join(self.failed_namespaces)) self.connected = True From bab4a10f48aaae11d7f832ebe5c30ad3f85d31b3 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 15 Oct 2025 09:17:11 +0100 Subject: [PATCH 142/173] Restore binary message support in message queue setups (Fixes #1508) (#1509) --- src/socketio/async_pubsub_manager.py | 14 +++++- src/socketio/packet.py | 40 +++++++++-------- src/socketio/pubsub_manager.py | 14 +++++- tests/async/test_pubsub_manager.py | 67 ++++++++++++++++++++++++++++ tests/common/test_packet.py | 24 ++++++---- tests/common/test_pubsub_manager.py | 65 +++++++++++++++++++++++++++ 6 files changed, 194 insertions(+), 30 deletions(-) diff --git a/src/socketio/async_pubsub_manager.py b/src/socketio/async_pubsub_manager.py index 9ccc3382..1bfcf9dc 100644 --- a/src/socketio/async_pubsub_manager.py +++ b/src/socketio/async_pubsub_manager.py @@ -1,10 +1,12 @@ import asyncio +import base64 from functools import partial import uuid from engineio import json from .async_manager import AsyncManager +from .packet import Packet class AsyncPubSubManager(AsyncManager): @@ -64,8 +66,12 @@ async def emit(self, event, data, namespace=None, room=None, skip_sid=None, callback = (room, namespace, id) else: callback = None + binary = Packet.data_is_binary(data) + if binary: + data, attachments = Packet.deconstruct_binary(data) + data = [data, *[base64.b64encode(a).decode() for a in attachments]] message = {'method': 'emit', 'event': event, 'data': data, - 'namespace': namespace, 'room': room, + 'binary': binary, 'namespace': namespace, 'room': room, 'skip_sid': skip_sid, 'callback': callback, 'host_id': self.host_id} await self._handle_emit(message) # handle in this host @@ -145,7 +151,11 @@ async def _handle_emit(self, message): *remote_callback) else: callback = None - await super().emit(message['event'], message['data'], + data = message['data'] + if message.get('binary'): + attachments = [base64.b64decode(a) for a in data[1:]] + data = Packet.reconstruct_binary(data[0], attachments) + await super().emit(message['event'], data, namespace=message.get('namespace'), room=message.get('room'), skip_sid=message.get('skip_sid'), diff --git a/src/socketio/packet.py b/src/socketio/packet.py index f7ad87e6..3deba7fb 100644 --- a/src/socketio/packet.py +++ b/src/socketio/packet.py @@ -29,7 +29,7 @@ def __init__(self, packet_type=EVENT, data=None, namespace=None, id=None, self.namespace = namespace self.id = id if self.uses_binary_events and \ - (binary or (binary is None and self._data_is_binary( + (binary or (binary is None and self.data_is_binary( self.data))): if self.packet_type == EVENT: self.packet_type = BINARY_EVENT @@ -51,7 +51,7 @@ def encode(self): """ encoded_packet = str(self.packet_type) if self.packet_type == BINARY_EVENT or self.packet_type == BINARY_ACK: - data, attachments = self._deconstruct_binary(self.data) + data, attachments = self.deconstruct_binary(self.data) encoded_packet += str(len(attachments)) + '-' else: data = self.data @@ -119,61 +119,65 @@ def add_attachment(self, attachment): raise ValueError('Unexpected binary attachment') self.attachments.append(attachment) if self.attachment_count == len(self.attachments): - self.reconstruct_binary(self.attachments) + self.data = self.reconstruct_binary(self.data, self.attachments) return True return False - def reconstruct_binary(self, attachments): + @classmethod + def reconstruct_binary(cls, data, attachments): """Reconstruct a decoded packet using the given list of binary attachments. """ - self.data = self._reconstruct_binary_internal(self.data, - self.attachments) + return cls._reconstruct_binary_internal(data, attachments) - def _reconstruct_binary_internal(self, data, attachments): + @classmethod + def _reconstruct_binary_internal(cls, data, attachments): if isinstance(data, list): - return [self._reconstruct_binary_internal(item, attachments) + return [cls._reconstruct_binary_internal(item, attachments) for item in data] elif isinstance(data, dict): if data.get('_placeholder') and 'num' in data: return attachments[data['num']] else: - return {key: self._reconstruct_binary_internal(value, - attachments) + return {key: cls._reconstruct_binary_internal(value, + attachments) for key, value in data.items()} else: return data - def _deconstruct_binary(self, data): + @classmethod + def deconstruct_binary(cls, data): """Extract binary components in the packet.""" attachments = [] - data = self._deconstruct_binary_internal(data, attachments) + data = cls._deconstruct_binary_internal(data, attachments) return data, attachments - def _deconstruct_binary_internal(self, data, attachments): + @classmethod + def _deconstruct_binary_internal(cls, data, attachments): if isinstance(data, bytes): attachments.append(data) return {'_placeholder': True, 'num': len(attachments) - 1} elif isinstance(data, list): - return [self._deconstruct_binary_internal(item, attachments) + return [cls._deconstruct_binary_internal(item, attachments) for item in data] elif isinstance(data, dict): - return {key: self._deconstruct_binary_internal(value, attachments) + return {key: cls._deconstruct_binary_internal(value, attachments) for key, value in data.items()} else: return data - def _data_is_binary(self, data): + @classmethod + def data_is_binary(cls, data): """Check if the data contains binary components.""" if isinstance(data, bytes): return True elif isinstance(data, list): return functools.reduce( - lambda a, b: a or b, [self._data_is_binary(item) + lambda a, b: a or b, [cls.data_is_binary(item) for item in data], False) elif isinstance(data, dict): return functools.reduce( - lambda a, b: a or b, [self._data_is_binary(item) + lambda a, b: a or b, [cls.data_is_binary(item) for item in data.values()], False) else: diff --git a/src/socketio/pubsub_manager.py b/src/socketio/pubsub_manager.py index 80744f2f..3528d228 100644 --- a/src/socketio/pubsub_manager.py +++ b/src/socketio/pubsub_manager.py @@ -1,9 +1,11 @@ +import base64 from functools import partial import uuid from engineio import json from .manager import Manager +from .packet import Packet class PubSubManager(Manager): @@ -61,8 +63,12 @@ def emit(self, event, data, namespace=None, room=None, skip_sid=None, callback = (room, namespace, id) else: callback = None + binary = Packet.data_is_binary(data) + if binary: + data, attachments = Packet.deconstruct_binary(data) + data = [data, *[base64.b64encode(a).decode() for a in attachments]] message = {'method': 'emit', 'event': event, 'data': data, - 'namespace': namespace, 'room': room, + 'binary': binary, 'namespace': namespace, 'room': room, 'skip_sid': skip_sid, 'callback': callback, 'host_id': self.host_id} self._handle_emit(message) # handle in this host @@ -141,7 +147,11 @@ def _handle_emit(self, message): *remote_callback) else: callback = None - super().emit(message['event'], message['data'], + data = message['data'] + if message.get('binary'): + attachments = [base64.b64decode(a) for a in data[1:]] + data = Packet.reconstruct_binary(data[0], attachments) + super().emit(message['event'], data, namespace=message.get('namespace'), room=message.get('room'), skip_sid=message.get('skip_sid'), callback=callback) diff --git a/tests/async/test_pubsub_manager.py b/tests/async/test_pubsub_manager.py index 4a7012ee..5d34e530 100644 --- a/tests/async/test_pubsub_manager.py +++ b/tests/async/test_pubsub_manager.py @@ -57,6 +57,7 @@ async def test_emit(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/', 'room': None, @@ -66,6 +67,36 @@ async def test_emit(self): } ) + async def test_emit_binary(self): + await self.pm.emit('foo', b'bar') + self.pm._publish.assert_awaited_once_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + await self.pm.emit('foo', {'foo': b'bar'}) + self.pm._publish.assert_awaited_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + async def test_emit_with_to(self): sid = 'room-mate' await self.pm.emit('foo', 'bar', to=sid) @@ -73,6 +104,7 @@ async def test_emit_with_to(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/', 'room': sid, @@ -88,6 +120,7 @@ async def test_emit_with_namespace(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/baz', 'room': None, @@ -103,6 +136,7 @@ async def test_emit_with_room(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/', 'room': 'baz', @@ -118,6 +152,7 @@ async def test_emit_with_skip_sid(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/', 'room': None, @@ -136,6 +171,7 @@ async def test_emit_with_callback(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/', 'room': 'baz', @@ -241,6 +277,37 @@ async def test_handle_emit(self): callback=None, ) + async def test_handle_emit_binary(self): + with mock.patch.object( + async_manager.AsyncManager, 'emit' + ) as super_emit: + await self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], + }) + super_emit.assert_awaited_once_with( + 'foo', + b'bar', + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + await self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], + }) + super_emit.assert_awaited_with( + 'foo', + {'foo': b'bar'}, + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + async def test_handle_emit_with_namespace(self): with mock.patch.object( async_manager.AsyncManager, 'emit' diff --git a/tests/common/test_packet.py b/tests/common/test_packet.py index 5682dab0..e15babfd 100644 --- a/tests/common/test_packet.py +++ b/tests/common/test_packet.py @@ -266,16 +266,24 @@ def test_decode_dash_in_payload(self): assert pkt.data["a"] == "0123456789-" assert pkt.attachment_count == 0 + def test_deconstruct_binary(self): + datas = [b'foo', [b'foo', b'bar'], ['foo', b'bar'], {'foo': b'bar'}, + {'foo': 'bar', 'baz': b'qux'}, {'foo': [b'bar']}] + for data in datas: + bdata, attachments = packet.Packet.deconstruct_binary(data) + rdata = packet.Packet.reconstruct_binary(bdata, attachments) + assert data == rdata + def test_data_is_binary_list(self): pkt = packet.Packet() - assert not pkt._data_is_binary(['foo']) - assert not pkt._data_is_binary([]) - assert pkt._data_is_binary([b'foo']) - assert pkt._data_is_binary(['foo', b'bar']) + assert not pkt.data_is_binary(['foo']) + assert not pkt.data_is_binary([]) + assert pkt.data_is_binary([b'foo']) + assert pkt.data_is_binary(['foo', b'bar']) def test_data_is_binary_dict(self): pkt = packet.Packet() - assert not pkt._data_is_binary({'a': 'foo'}) - assert not pkt._data_is_binary({}) - assert pkt._data_is_binary({'a': b'foo'}) - assert pkt._data_is_binary({'a': 'foo', 'b': b'bar'}) + assert not pkt.data_is_binary({'a': 'foo'}) + assert not pkt.data_is_binary({}) + assert pkt.data_is_binary({'a': b'foo'}) + assert pkt.data_is_binary({'a': 'foo', 'b': b'bar'}) diff --git a/tests/common/test_pubsub_manager.py b/tests/common/test_pubsub_manager.py index 32c7d41d..5ef54734 100644 --- a/tests/common/test_pubsub_manager.py +++ b/tests/common/test_pubsub_manager.py @@ -69,6 +69,7 @@ def test_emit(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/', 'room': None, @@ -78,6 +79,36 @@ def test_emit(self): } ) + def test_emit_binary(self): + self.pm.emit('foo', b'bar') + self.pm._publish.assert_called_once_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + self.pm.emit('foo', {'foo': b'bar'}) + self.pm._publish.assert_called_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + def test_emit_with_to(self): sid = "ferris" self.pm.emit('foo', 'bar', to=sid) @@ -85,6 +116,7 @@ def test_emit_with_to(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/', 'room': sid, @@ -100,6 +132,7 @@ def test_emit_with_namespace(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/baz', 'room': None, @@ -115,6 +148,7 @@ def test_emit_with_room(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/', 'room': 'baz', @@ -130,6 +164,7 @@ def test_emit_with_skip_sid(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/', 'room': None, @@ -148,6 +183,7 @@ def test_emit_with_callback(self): { 'method': 'emit', 'event': 'foo', + 'binary': False, 'data': 'bar', 'namespace': '/', 'room': 'baz', @@ -250,6 +286,35 @@ def test_handle_emit(self): callback=None, ) + def test_handle_emit_binary(self): + with mock.patch.object(manager.Manager, 'emit') as super_emit: + self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], + }) + super_emit.assert_called_once_with( + 'foo', + b'bar', + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], + }) + super_emit.assert_called_with( + 'foo', + {'foo': b'bar'}, + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + def test_handle_emit_with_namespace(self): with mock.patch.object(manager.Manager, 'emit') as super_emit: self.pm._handle_emit( From 1f4cd3b025c294f25208ec3c05b5f8df6209e403 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 15 Oct 2025 19:30:30 +0100 Subject: [PATCH 143/173] Add 3.14 and pypy-3.11 CI tasks --- .github/workflows/tests.yml | 4 ++-- pyproject.toml | 3 +++ tox.ini | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9d2f4759..ee924cf3 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,11 +16,11 @@ jobs: strategy: matrix: os: [windows-latest, macos-latest, ubuntu-latest] - python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', 'pypy-3.10'] + python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 'pypy-3.11'] exclude: # pypy3 currently fails to run on Windows - os: windows-latest - python: pypy-3.10 + python: pypy-3.11 fail-fast: false runs-on: ${{ matrix.os }} steps: diff --git a/pyproject.toml b/pyproject.toml index f3daf2dc..12289037 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,6 +34,9 @@ client = [ asyncio_client = [ "aiohttp >= 3.4", ] +dev = [ + "tox", +] docs = [ "sphinx", ] diff --git a/tox.ini b/tox.ini index bb830590..b73e4bbc 100644 --- a/tox.ini +++ b/tox.ini @@ -1,5 +1,5 @@ [tox] -envlist=flake8,py{38,39,310,311,312,313},docs +envlist=flake8,py{38,39,310,311,312,313,314},docs skip_missing_interpreters=True [gh-actions] @@ -10,6 +10,7 @@ python = 3.11: py311 3.12: py312 3.13: py313 + 3.14: py314 pypy-3: pypy3 [testenv] From 8fcbd80e769695cbe2ec980ee39fb8a7a7e738e6 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 15 Oct 2025 19:57:20 +0100 Subject: [PATCH 144/173] Remove old build matrix exceptions #nolog (#1510) --- .github/workflows/tests.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ee924cf3..6a8433e4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -17,10 +17,6 @@ jobs: matrix: os: [windows-latest, macos-latest, ubuntu-latest] python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 'pypy-3.11'] - exclude: - # pypy3 currently fails to run on Windows - - os: windows-latest - python: pypy-3.11 fail-fast: false runs-on: ${{ matrix.os }} steps: From 08f65c22bb5437a2cdbde03212c2c60dd3b447b7 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 15 Oct 2025 19:59:08 +0100 Subject: [PATCH 145/173] Release 5.14.2 --- CHANGES.md | 7 +++++++ pyproject.toml | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fe5406bb..60955af1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,12 @@ # python-socketio change log +**Release 5.14.2** - 2025-10-15 + +- Restore binary message support in message queue setups [#1509](https://github.com/miguelgrinberg/python-socketio/issues/1509) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/bab4a10f48aaae11d7f832ebe5c30ad3f85d31b3)) +- Fix formatting of client connection error [#1507](https://github.com/miguelgrinberg/python-socketio/issues/1507) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/f298c9b54d76ab09ff72935937e1b9575bc45ffd)) +- Add 3.14 and pypy-3.11 CI tasks ([commit](https://github.com/miguelgrinberg/python-socketio/commit/1f4cd3b025c294f25208ec3c05b5f8df6209e403)) +- Improve documentation of the `BaseManager.get_participants()` method ([commit](https://github.com/miguelgrinberg/python-socketio/commit/33722a0d96036f005188b07b8b46a5ef091fe65f)) + **Release 5.14.1** - 2025-10-02 - Restore support for `rediss://` URLs, and add support for `valkeys://` as well ([commit](https://github.com/miguelgrinberg/python-socketio/commit/6e2d0de12bb4e4a99fdfc30bed0706ded620822c)) diff --git a/pyproject.toml b/pyproject.toml index 12289037..7e3b2377 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.14.2.dev0" +version = "5.14.2" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From f8a1d435b23df74e720cb1c384955ff5dd1c442c Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 15 Oct 2025 19:59:45 +0100 Subject: [PATCH 146/173] Version 5.14.3.dev0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 7e3b2377..1148e501 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.14.2" +version = "5.14.3.dev0" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From 194e1b7f277b5f72e1de78d3f614e7b8b6c788ac Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 28 Oct 2025 20:24:54 +0000 Subject: [PATCH 147/173] Only push binary data to aiopika (Fixes #1513) (#1514) --- src/socketio/async_aiopika_manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socketio/async_aiopika_manager.py b/src/socketio/async_aiopika_manager.py index 1485d37a..60be8877 100644 --- a/src/socketio/async_aiopika_manager.py +++ b/src/socketio/async_aiopika_manager.py @@ -82,7 +82,7 @@ async def _publish(self, data): try: await self.publisher_exchange.publish( aio_pika.Message( - body=json.dumps(data), + body=json.dumps(data).encode(), delivery_mode=aio_pika.DeliveryMode.PERSISTENT ), routing_key='*', ) From f3b18bde3f16437b223491d4c3e440ea37105fe3 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 28 Oct 2025 23:43:05 +0000 Subject: [PATCH 148/173] Support Python's ConnectionRefusedError to reject a connection (#1515) * Support Python's ConnectionRefusedError to reject a connection * unit tests --- docs/server.rst | 2 ++ src/socketio/async_server.py | 3 +++ src/socketio/server.py | 3 +++ tests/async/test_server.py | 15 +++++++++++++++ tests/common/test_server.py | 14 ++++++++++++++ 5 files changed, 37 insertions(+) diff --git a/docs/server.rst b/docs/server.rst index 9261a425..1bc4c68a 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -246,6 +246,8 @@ a :class:`socketio.exceptions.ConnectionRefusedError` exception can be raised, and all of its arguments will be sent to the client with the rejection message:: + from socketio.exceptions import ConnectionRefusedError + @sio.event def connect(sid, environ, auth): raise ConnectionRefusedError('authentication failed') diff --git a/src/socketio/async_server.py b/src/socketio/async_server.py index fac0f2b0..6c9e3ca3 100644 --- a/src/socketio/async_server.py +++ b/src/socketio/async_server.py @@ -561,6 +561,9 @@ async def _handle_connect(self, eio_sid, namespace, data): except exceptions.ConnectionRefusedError as exc: fail_reason = exc.error_args success = False + except ConnectionRefusedError: + fail_reason = {"message": "Connection refused by server"} + success = False if success is False: if self.always_connect: diff --git a/src/socketio/server.py b/src/socketio/server.py index 71c702de..f3257081 100644 --- a/src/socketio/server.py +++ b/src/socketio/server.py @@ -543,6 +543,9 @@ def _handle_connect(self, eio_sid, namespace, data): except exceptions.ConnectionRefusedError as exc: fail_reason = exc.error_args success = False + except ConnectionRefusedError: + fail_reason = {"message": "Connection refused by server"} + success = False if success is False: if self.always_connect: diff --git a/tests/async/test_server.py b/tests/async/test_server.py index f60de27a..575f2097 100644 --- a/tests/async/test_server.py +++ b/tests/async/test_server.py @@ -482,6 +482,21 @@ async def test_handle_connect_rejected_with_exception(self, eio): '123', '4{"message":"fail_reason"}') assert s.environ == {'123': 'environ'} + async def test_handle_connect_rejected_with_python_exception(self, eio): + eio.return_value.send = mock.AsyncMock() + s = async_server.AsyncServer() + handler = mock.MagicMock( + side_effect=ConnectionRefusedError() + ) + s.on('connect', handler) + await s._handle_eio_connect('123', 'environ') + await s._handle_eio_message('123', '0') + assert not s.manager.is_connected('1', '/') + handler.assert_called_once_with('1', 'environ') + s.eio.send.assert_awaited_once_with( + '123', '4{"message":"Connection refused by server"}') + assert s.environ == {'123': 'environ'} + async def test_handle_connect_rejected_with_empty_exception(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() diff --git a/tests/common/test_server.py b/tests/common/test_server.py index 445d5d9e..bdbbfe07 100644 --- a/tests/common/test_server.py +++ b/tests/common/test_server.py @@ -462,6 +462,20 @@ def test_handle_connect_rejected_with_exception(self, eio): s.eio.send.assert_called_once_with('123', '4{"message":"fail_reason"}') assert s.environ == {'123': 'environ'} + def test_handle_connect_rejected_with_python_exception(self, eio): + s = server.Server() + handler = mock.MagicMock( + side_effect=ConnectionRefusedError() + ) + s.on('connect', handler) + s._handle_eio_connect('123', 'environ') + s._handle_eio_message('123', '0') + assert not s.manager.is_connected('1', '/') + handler.assert_called_once_with('1', 'environ') + s.eio.send.assert_called_once_with( + '123', '4{"message":"Connection refused by server"}') + assert s.environ == {'123': 'environ'} + def test_handle_connect_rejected_with_empty_exception(self, eio): s = server.Server() handler = mock.MagicMock( From 227e53bdf7d6877f1c7445d4ffeb8793a18c79f8 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 29 Oct 2025 09:42:20 +0000 Subject: [PATCH 149/173] Release 5.14.3 --- CHANGES.md | 5 +++++ pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 60955af1..fe2db445 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # python-socketio change log +**Release 5.14.3** - 2025-10-29 + +- Support Python's native `ConnectionRefusedError` exception to reject a connection [#1515](https://github.com/miguelgrinberg/python-socketio/issues/1515) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/f3b18bde3f16437b223491d4c3e440ea37105fe3)) +- Push binary data to the aiopika client manager [#1514](https://github.com/miguelgrinberg/python-socketio/issues/1514) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/194e1b7f277b5f72e1de78d3f614e7b8b6c788ac)) + **Release 5.14.2** - 2025-10-15 - Restore binary message support in message queue setups [#1509](https://github.com/miguelgrinberg/python-socketio/issues/1509) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/bab4a10f48aaae11d7f832ebe5c30ad3f85d31b3)) diff --git a/pyproject.toml b/pyproject.toml index 1148e501..4d8cad9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.14.3.dev0" +version = "5.14.3" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From 54b4382b5fba05307694954e375f60bcae1a5a17 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 29 Oct 2025 09:42:55 +0000 Subject: [PATCH 150/173] Version 5.14.4.dev0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 4d8cad9d..f444d74d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.14.3" +version = "5.14.4.dev0" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From c52e93b4a328d98a968bfbdec0cfd598b73ee913 Mon Sep 17 00:00:00 2001 From: Gritty_dev <101377478+codomposer@users.noreply.github.com> Date: Thu, 30 Oct 2025 16:21:35 -0400 Subject: [PATCH 151/173] fix recreate binding on failure (#1516) --- src/socketio/async_aiopika_manager.py | 36 +++++++++++++-------------- src/socketio/kombu_manager.py | 2 +- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/src/socketio/async_aiopika_manager.py b/src/socketio/async_aiopika_manager.py index 60be8877..34171c46 100644 --- a/src/socketio/async_aiopika_manager.py +++ b/src/socketio/async_aiopika_manager.py @@ -101,26 +101,26 @@ async def _publish(self, data): raise asyncio.CancelledError() async def _listen(self): - async with (await self._connection()) as connection: - channel = await self._channel(connection) - await channel.set_qos(prefetch_count=1) - exchange = await self._exchange(channel) - queue = await self._queue(channel, exchange) - - retry_sleep = 1 - while True: - try: + retry_sleep = 1 + while True: + try: + async with (await self._connection()) as connection: + channel = await self._channel(connection) + await channel.set_qos(prefetch_count=1) + exchange = await self._exchange(channel) + queue = await self._queue(channel, exchange) + async with queue.iterator() as queue_iter: async for message in queue_iter: async with message.process(): yield message.body retry_sleep = 1 - except aio_pika.AMQPException: - self._get_logger().error( - 'Cannot receive from rabbitmq... ' - 'retrying in {} secs'.format(retry_sleep)) - await asyncio.sleep(retry_sleep) - retry_sleep = min(retry_sleep * 2, 60) - except aio_pika.exceptions.ChannelInvalidStateError: - # aio_pika raises this exception when the task is cancelled - raise asyncio.CancelledError() + except aio_pika.AMQPException: + self._get_logger().error( + 'Cannot receive from rabbitmq... ' + 'retrying in {} secs'.format(retry_sleep)) + await asyncio.sleep(retry_sleep) + retry_sleep = min(retry_sleep * 2, 60) + except aio_pika.exceptions.ChannelInvalidStateError: + # aio_pika raises this exception when the task is cancelled + raise asyncio.CancelledError() diff --git a/src/socketio/kombu_manager.py b/src/socketio/kombu_manager.py index bc84bbb9..ae9d1393 100644 --- a/src/socketio/kombu_manager.py +++ b/src/socketio/kombu_manager.py @@ -115,10 +115,10 @@ def _publish(self, data): break def _listen(self): - reader_queue = self._queue() retry_sleep = 1 while True: try: + reader_queue = self._queue() with self._connection() as connection: with connection.SimpleQueue(reader_queue) as queue: while True: From b423d0e38eef559b7e81acb7e32059de305f982c Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Fri, 31 Oct 2025 09:11:18 +0000 Subject: [PATCH 152/173] Improvements to the logging documentation --- docs/client.rst | 11 ++++++++--- docs/server.rst | 11 ++++++++--- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/docs/client.rst b/docs/client.rst index e3e1fb2c..64cbec88 100644 --- a/docs/client.rst +++ b/docs/client.rst @@ -198,9 +198,14 @@ terminal:: The ``logger`` argument controls logging related to the Socket.IO protocol, while ``engineio_logger`` controls logs that originate in the low-level -Engine.IO transport. These arguments can be set to ``True`` to output logs to -``stderr``, or to an object compatible with Python's ``logging`` package -where the logs should be emitted to. A value of ``False`` disables logging. +Engine.IO transport. The value given to these arguments controls logging +behavior: + +* ``True``: Enables log output to ``stderr`` at the ``INFO`` level. +* ``False``: Enables log output to ``stderr`` at the ``ERROR`` level. This is + the default. +* A ``logging.Logger`` instance: Uses the provided logger without additional + configuration. Logging can help identify the cause of connection problems, unexpected disconnections and other issues. diff --git a/docs/server.rst b/docs/server.rst index 1bc4c68a..f31e6680 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -657,9 +657,14 @@ terminal:: The ``logger`` argument controls logging related to the Socket.IO protocol, while ``engineio_logger`` controls logs that originate in the low-level -Engine.IO transport. These arguments can be set to ``True`` to output logs to -``stderr``, or to an object compatible with Python's ``logging`` package -where the logs should be emitted to. A value of ``False`` disables logging. +Engine.IO transport. The value given to these arguments controls logging +behavior: + +* ``True``: Enables log output to ``stderr`` at the ``INFO`` level. +* ``False``: Enables log output to ``stderr`` at the ``ERROR`` level. This is + the default. +* A ``logging.Logger`` instance: Uses the provided logger without additional + configuration. Logging can help identify the cause of connection problems, 400 responses, bad performance and other issues. From f4b4a98bda0e8c68f0424bf1306eebb860bb9366 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 6 Nov 2025 00:35:42 +0000 Subject: [PATCH 153/173] Bump django in /examples/server/wsgi/django_socketio (#1519) #nolog Bumps [django](https://github.com/django/django) from 4.2.22 to 4.2.26. - [Commits](https://github.com/django/django/compare/4.2.22...4.2.26) --- updated-dependencies: - dependency-name: django dependency-version: 4.2.26 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/django_socketio/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/django_socketio/requirements.txt b/examples/server/wsgi/django_socketio/requirements.txt index 39eae80f..065a4405 100644 --- a/examples/server/wsgi/django_socketio/requirements.txt +++ b/examples/server/wsgi/django_socketio/requirements.txt @@ -1,6 +1,6 @@ asgiref==3.6.0 bidict==0.22.1 -Django==4.2.22 +Django==4.2.26 gunicorn==23.0.0 h11==0.16.0 python-engineio From db3f1c2a0105c30cb833ddfca8f05fe4320468fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=AA=20Nam=20Kh=C3=A1nh?= <55955273+khanhkhanhlele@users.noreply.github.com> Date: Thu, 6 Nov 2025 17:32:33 +0700 Subject: [PATCH 154/173] Fix typos in src/socketio/async_client.py (#1520) --- src/socketio/async_client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index 678743a2..ead963d0 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -489,7 +489,7 @@ async def _trigger_event(self, event, namespace, *args): raise return ret - # or else, forward the event to a namepsace handler if one exists + # or else, forward the event to a namespace handler if one exists handler, args = self._get_namespace_handler(namespace, args) if handler: return await handler.trigger_event(event, *args) From 6c9b9974f72e2efdf62407ecab24ee6995448098 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 16 Nov 2025 13:38:29 +0000 Subject: [PATCH 155/173] Support sending bytestrings when using pub/sub managers --- src/socketio/packet.py | 4 ++-- tests/async/test_pubsub_manager.py | 30 +++++++++++++++++++++++++++++ tests/common/test_packet.py | 4 ++++ tests/common/test_pubsub_manager.py | 30 +++++++++++++++++++++++++++++ 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/socketio/packet.py b/src/socketio/packet.py index 3deba7fb..101455ea 100644 --- a/src/socketio/packet.py +++ b/src/socketio/packet.py @@ -154,7 +154,7 @@ def deconstruct_binary(cls, data): @classmethod def _deconstruct_binary_internal(cls, data, attachments): - if isinstance(data, bytes): + if isinstance(data, (bytes, bytearray)): attachments.append(data) return {'_placeholder': True, 'num': len(attachments) - 1} elif isinstance(data, list): @@ -169,7 +169,7 @@ def _deconstruct_binary_internal(cls, data, attachments): @classmethod def data_is_binary(cls, data): """Check if the data contains binary components.""" - if isinstance(data, bytes): + if isinstance(data, (bytes, bytearray)): return True elif isinstance(data, list): return functools.reduce( diff --git a/tests/async/test_pubsub_manager.py b/tests/async/test_pubsub_manager.py index 5d34e530..f9cfb7eb 100644 --- a/tests/async/test_pubsub_manager.py +++ b/tests/async/test_pubsub_manager.py @@ -97,6 +97,36 @@ async def test_emit_binary(self): } ) + async def test_emit_bytearray(self): + await self.pm.emit('foo', bytearray(b'bar')) + self.pm._publish.assert_awaited_once_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + await self.pm.emit('foo', {'foo': bytearray(b'bar')}) + self.pm._publish.assert_awaited_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + async def test_emit_with_to(self): sid = 'room-mate' await self.pm.emit('foo', 'bar', to=sid) diff --git a/tests/common/test_packet.py b/tests/common/test_packet.py index e15babfd..e965313a 100644 --- a/tests/common/test_packet.py +++ b/tests/common/test_packet.py @@ -279,11 +279,15 @@ def test_data_is_binary_list(self): assert not pkt.data_is_binary(['foo']) assert not pkt.data_is_binary([]) assert pkt.data_is_binary([b'foo']) + assert pkt.data_is_binary([bytearray(b'foo')]) assert pkt.data_is_binary(['foo', b'bar']) + assert pkt.data_is_binary(['foo', bytearray(b'bar')]) def test_data_is_binary_dict(self): pkt = packet.Packet() assert not pkt.data_is_binary({'a': 'foo'}) assert not pkt.data_is_binary({}) assert pkt.data_is_binary({'a': b'foo'}) + assert pkt.data_is_binary({'a': bytearray(b'foo')}) assert pkt.data_is_binary({'a': 'foo', 'b': b'bar'}) + assert pkt.data_is_binary({'a': 'foo', 'b': bytearray(b'bar')}) diff --git a/tests/common/test_pubsub_manager.py b/tests/common/test_pubsub_manager.py index 5ef54734..abef2bf2 100644 --- a/tests/common/test_pubsub_manager.py +++ b/tests/common/test_pubsub_manager.py @@ -109,6 +109,36 @@ def test_emit_binary(self): } ) + def test_emit_bytearray(self): + self.pm.emit('foo', bytearray(b'bar')) + self.pm._publish.assert_called_once_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + self.pm.emit('foo', {'foo': bytearray(b'bar')}) + self.pm._publish.assert_called_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + def test_emit_with_to(self): sid = "ferris" self.pm.emit('foo', 'bar', to=sid) From 208925344a48485d2cd56e40eb74266c3bcb5311 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Fri, 21 Nov 2025 23:22:49 +0000 Subject: [PATCH 156/173] Support ext_type in the MsgPackPacket class (#1521) --- src/socketio/msgpack_packet.py | 30 +++++++- tests/async/test_client.py | 20 ++++++ tests/async/test_server.py | 20 ++++++ tests/common/test_client.py | 20 ++++++ tests/common/test_msgpack_packet.py | 104 ++++++++++++++++++++++++++++ tests/common/test_server.py | 20 ++++++ 6 files changed, 212 insertions(+), 2 deletions(-) diff --git a/src/socketio/msgpack_packet.py b/src/socketio/msgpack_packet.py index 27462634..732d1882 100644 --- a/src/socketio/msgpack_packet.py +++ b/src/socketio/msgpack_packet.py @@ -4,14 +4,40 @@ class MsgPackPacket(packet.Packet): uses_binary_events = False + dumps_default = None + ext_hook = msgpack.ExtType + + @classmethod + def configure(cls, dumps_default=None, ext_hook=msgpack.ExtType): + """Change the default options for msgpack encoding and decoding. + + :param dumps_default: a function called for objects that cannot be + serialized by default msgpack. The function + receives one argument, the object to serialize. + It should return a serializable object or a + ``msgpack.ExtType`` instance. + :param ext_hook: a function called when a ``msgpack.ExtType`` object is + seen during decoding. The function receives two + arguments, the code and the data. It should return the + decoded object. + """ + class CustomMsgPackPacket(MsgPackPacket): + dumps_default = None + ext_hook = None + + CustomMsgPackPacket.dumps_default = dumps_default + CustomMsgPackPacket.ext_hook = ext_hook + return CustomMsgPackPacket def encode(self): """Encode the packet for transmission.""" - return msgpack.dumps(self._to_dict()) + return msgpack.dumps(self._to_dict(), + default=self.__class__.dumps_default) def decode(self, encoded_packet): """Decode a transmitted package.""" - decoded = msgpack.loads(encoded_packet) + decoded = msgpack.loads(encoded_packet, + ext_hook=self.__class__.ext_hook) self.packet_type = decoded['type'] self.data = decoded.get('data') self.id = decoded.get('id') diff --git a/tests/async/test_client.py b/tests/async/test_client.py index b4b0c6c5..7a7bfa7c 100644 --- a/tests/async/test_client.py +++ b/tests/async/test_client.py @@ -1,5 +1,6 @@ import asyncio from unittest import mock +from datetime import datetime, timezone, timedelta import pytest @@ -8,6 +9,7 @@ from engineio import exceptions as engineio_exceptions from socketio import exceptions from socketio import packet +from socketio.msgpack_packet import MsgPackPacket class TestAsyncClient: @@ -1242,3 +1244,21 @@ async def test_eio_disconnect_no_reconnect(self): assert c.sid is None assert not c.connected c.start_background_task.assert_not_called() + + def test_serializer_args_with_msgpack(self): + def default(o): + if isinstance(o, datetime): + return o.isoformat() + raise TypeError("Unknown type") + + data = {"current": datetime.now(timezone(timedelta(0)))} + c = async_client.AsyncClient( + serializer=MsgPackPacket.configure(dumps_default=default)) + p = c.packet_class(data=data) + p2 = c.packet_class(encoded_packet=p.encode()) + + assert p.data != p2.data + assert isinstance(p2.data, dict) + assert "current" in p2.data + assert isinstance(p2.data["current"], str) + assert default(data["current"]) == p2.data["current"] diff --git a/tests/async/test_server.py b/tests/async/test_server.py index 575f2097..10d7ba14 100644 --- a/tests/async/test_server.py +++ b/tests/async/test_server.py @@ -1,6 +1,7 @@ import asyncio import logging from unittest import mock +from datetime import datetime, timezone, timedelta from engineio import json from engineio import packet as eio_packet @@ -11,6 +12,7 @@ from socketio import exceptions from socketio import namespace from socketio import packet +from socketio.msgpack_packet import MsgPackPacket @mock.patch('socketio.server.engineio.AsyncServer', **{ @@ -1089,3 +1091,21 @@ async def test_sleep(self, eio): s = async_server.AsyncServer() await s.sleep(1.23) s.eio.sleep.assert_awaited_once_with(1.23) + + def test_serializer_args_with_msgpack(self, eio): + def default(o): + if isinstance(o, datetime): + return o.isoformat() + raise TypeError("Unknown type") + + data = {"current": datetime.now(timezone(timedelta(0)))} + s = async_server.AsyncServer( + serializer=MsgPackPacket.configure(dumps_default=default)) + p = s.packet_class(data=data) + p2 = s.packet_class(encoded_packet=p.encode()) + + assert p.data != p2.data + assert isinstance(p2.data, dict) + assert "current" in p2.data + assert isinstance(p2.data["current"], str) + assert default(data["current"]) == p2.data["current"] diff --git a/tests/common/test_client.py b/tests/common/test_client.py index cbda3f1f..d386a9c3 100644 --- a/tests/common/test_client.py +++ b/tests/common/test_client.py @@ -1,6 +1,7 @@ import logging import time from unittest import mock +from datetime import datetime, timezone, timedelta from engineio import exceptions as engineio_exceptions from engineio import json @@ -13,6 +14,7 @@ from socketio import msgpack_packet from socketio import namespace from socketio import packet +from socketio.msgpack_packet import MsgPackPacket class TestClient: @@ -1386,3 +1388,21 @@ def test_eio_disconnect_no_reconnect(self): assert c.sid is None assert not c.connected c.start_background_task.assert_not_called() + + def test_serializer_args_with_msgpack(self): + def default(o): + if isinstance(o, datetime): + return o.isoformat() + raise TypeError("Unknown type") + + data = {"current": datetime.now(timezone(timedelta(0)))} + c = client.Client( + serializer=MsgPackPacket.configure(dumps_default=default)) + p = c.packet_class(data=data) + p2 = c.packet_class(encoded_packet=p.encode()) + + assert p.data != p2.data + assert isinstance(p2.data, dict) + assert "current" in p2.data + assert isinstance(p2.data["current"], str) + assert default(data["current"]) == p2.data["current"] diff --git a/tests/common/test_msgpack_packet.py b/tests/common/test_msgpack_packet.py index e0197a27..0fad0292 100644 --- a/tests/common/test_msgpack_packet.py +++ b/tests/common/test_msgpack_packet.py @@ -1,3 +1,8 @@ +from datetime import datetime, timedelta, timezone + +import pytest +import msgpack + from socketio import msgpack_packet from socketio import packet @@ -32,3 +37,102 @@ def test_encode_binary_ack_packet(self): assert p.packet_type == packet.ACK p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode()) assert p2.data == {'foo': b'bar'} + + def test_encode_with_dumps_default(self): + def default(obj): + if isinstance(obj, datetime): + return obj.isoformat() + raise TypeError('Unknown type') + + data = { + 'current': datetime.now(tz=timezone(timedelta(0))), + 'key': 'value', + } + p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)( + data=data) + p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode()) + assert p.packet_type == p2.packet_type + assert p.id == p2.id + assert p.namespace == p2.namespace + assert p.data != p2.data + + assert isinstance(p2.data, dict) + assert 'current' in p2.data + assert isinstance(p2.data['current'], str) + assert default(data['current']) == p2.data['current'] + + data.pop('current') + p2_data_without_current = p2.data.copy() + p2_data_without_current.pop('current') + assert data == p2_data_without_current + + def test_encode_without_dumps_default(self): + data = { + 'current': datetime.now(tz=timezone(timedelta(0))), + 'key': 'value', + } + p_without_default = msgpack_packet.MsgPackPacket(data=data) + with pytest.raises(TypeError): + p_without_default.encode() + + def test_encode_decode_with_ext_hook(self): + class Custom: + def __init__(self, value): + self.value = value + + def __eq__(self, value: object) -> bool: + return isinstance(value, Custom) and self.value == value.value + + def default(obj): + if isinstance(obj, Custom): + return msgpack.ExtType(1, obj.value) + raise TypeError('Unknown type') + + def ext_hook(code, data): + if code == 1: + return Custom(data) + raise TypeError('Unknown ext type') + + data = {'custom': Custom(b'custom_data'), 'key': 'value'} + p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)( + data=data) + p2 = msgpack_packet.MsgPackPacket.configure(ext_hook=ext_hook)( + encoded_packet=p.encode() + ) + assert p.packet_type == p2.packet_type + assert p.id == p2.id + assert p.data == p2.data + assert p.namespace == p2.namespace + + def test_encode_decode_without_ext_hook(self): + class Custom: + def __init__(self, value): + self.value = value + + def __eq__(self, value: object) -> bool: + return isinstance(value, Custom) and self.value == value.value + + def default(obj): + if isinstance(obj, Custom): + return msgpack.ExtType(1, obj.value) + raise TypeError('Unknown type') + + data = {'custom': Custom(b'custom_data'), 'key': 'value'} + p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)( + data=data) + p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode()) + assert p.packet_type == p2.packet_type + assert p.id == p2.id + assert p.namespace == p2.namespace + assert p.data != p2.data + + assert isinstance(p2.data, dict) + assert 'custom' in p2.data + assert isinstance(p2.data['custom'], msgpack.ExtType) + assert p2.data['custom'].code == 1 + assert p2.data['custom'].data == b'custom_data' + + data.pop('custom') + p2_data_without_custom = p2.data.copy() + p2_data_without_custom.pop('custom') + assert data == p2_data_without_custom diff --git a/tests/common/test_server.py b/tests/common/test_server.py index bdbbfe07..4c2c8071 100644 --- a/tests/common/test_server.py +++ b/tests/common/test_server.py @@ -1,5 +1,6 @@ import logging from unittest import mock +from datetime import datetime, timezone, timedelta from engineio import json from engineio import packet as eio_packet @@ -10,6 +11,7 @@ from socketio import namespace from socketio import packet from socketio import server +from socketio.msgpack_packet import MsgPackPacket @mock.patch('socketio.server.engineio.Server', **{ @@ -1032,3 +1034,21 @@ def test_sleep(self, eio): s = server.Server() s.sleep(1.23) s.eio.sleep.assert_called_once_with(1.23) + + def test_serializer_args_with_msgpack(self, eio): + def default(o): + if isinstance(o, datetime): + return o.isoformat() + raise TypeError("Unknown type") + + data = {"current": datetime.now(timezone(timedelta(0)))} + s = server.Server( + serializer=MsgPackPacket.configure(dumps_default=default)) + p = s.packet_class(data=data) + p2 = s.packet_class(encoded_packet=p.encode()) + + assert p.data != p2.data + assert isinstance(p2.data, dict) + assert "current" in p2.data + assert isinstance(p2.data["current"], str) + assert default(data["current"]) == p2.data["current"] From 1e903e173a2d7b04599c4f7f9630c1abbb531fad Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 22 Nov 2025 12:21:54 +0000 Subject: [PATCH 157/173] Retry initial Redis connection (Fixes #1534) (#1536) --- src/socketio/async_redis_manager.py | 20 ++++++++++---------- src/socketio/redis_manager.py | 20 ++++++++++---------- tests/async/test_redis_manager.py | 16 ++++++++++------ tests/common/test_redis_manager.py | 16 ++++++++++------ 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/src/socketio/async_redis_manager.py b/src/socketio/async_redis_manager.py index b8ac4a0f..1cb7fb9d 100644 --- a/src/socketio/async_redis_manager.py +++ b/src/socketio/async_redis_manager.py @@ -61,7 +61,9 @@ def __init__(self, url='redis://localhost:6379/0', channel='socketio', super().__init__(channel=channel, write_only=write_only, logger=logger) self.redis_url = url self.redis_options = redis_options or {} - self._redis_connect() + self.connected = False + self.redis = None + self.pubsub = None def _get_redis_module_and_error(self): parsed_url = urlparse(self.redis_url) @@ -106,23 +108,23 @@ def _redis_connect(self): self.redis = module.Redis.from_url(self.redis_url, **self.redis_options) self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True) + self.connected = True async def _publish(self, data): # pragma: no cover - retry = True _, error = self._get_redis_module_and_error() - while True: + for retries_left in range(1, -1, -1): # 2 attempts try: - if not retry: + if not self.connected: self._redis_connect() return await self.redis.publish( self.channel, json.dumps(data)) except error as exc: - if retry: + if retries_left > 0: self._get_logger().error( 'Cannot publish to redis... ' 'retrying', extra={"redis_exception": str(exc)}) - retry = False + self.connected = False else: self._get_logger().error( 'Cannot publish to redis... ' @@ -133,11 +135,10 @@ async def _publish(self, data): # pragma: no cover async def _redis_listen_with_retries(self): # pragma: no cover retry_sleep = 1 - connect = False _, error = self._get_redis_module_and_error() while True: try: - if connect: + if not self.connected: self._redis_connect() await self.pubsub.subscribe(self.channel) retry_sleep = 1 @@ -148,7 +149,7 @@ async def _redis_listen_with_retries(self): # pragma: no cover 'retrying in ' f'{retry_sleep} secs', extra={"redis_exception": str(exc)}) - connect = True + self.connected = False await asyncio.sleep(retry_sleep) retry_sleep *= 2 if retry_sleep > 60: @@ -156,7 +157,6 @@ async def _redis_listen_with_retries(self): # pragma: no cover async def _listen(self): # pragma: no cover channel = self.channel.encode('utf-8') - await self.pubsub.subscribe(self.channel) async for message in self._redis_listen_with_retries(): if message['channel'] == channel and \ message['type'] == 'message' and 'data' in message: diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index 5e58ef41..827918b7 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -83,7 +83,9 @@ def __init__(self, url='redis://localhost:6379/0', channel='socketio', super().__init__(channel=channel, write_only=write_only, logger=logger) self.redis_url = url self.redis_options = redis_options or {} - self._redis_connect() + self.connected = False + self.redis = None + self.pubsub = None def initialize(self): # pragma: no cover super().initialize() @@ -143,22 +145,22 @@ def _redis_connect(self): self.redis = module.Redis.from_url(self.redis_url, **self.redis_options) self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True) + self.connected = True def _publish(self, data): # pragma: no cover - retry = True _, error = self._get_redis_module_and_error() - while True: + for retries_left in range(1, -1, -1): # 2 attempts try: - if not retry: + if not self.connected: self._redis_connect() return self.redis.publish(self.channel, json.dumps(data)) except error as exc: - if retry: + if retries_left > 0: logger.error( 'Cannot publish to redis... retrying', extra={"redis_exception": str(exc)} ) - retry = False + self.connected = False else: logger.error( 'Cannot publish to redis... giving up', @@ -168,11 +170,10 @@ def _publish(self, data): # pragma: no cover def _redis_listen_with_retries(self): # pragma: no cover retry_sleep = 1 - connect = False _, error = self._get_redis_module_and_error() while True: try: - if connect: + if not self.connected: self._redis_connect() self.pubsub.subscribe(self.channel) retry_sleep = 1 @@ -181,7 +182,7 @@ def _redis_listen_with_retries(self): # pragma: no cover logger.error('Cannot receive from redis... ' f'retrying in {retry_sleep} secs', extra={"redis_exception": str(exc)}) - connect = True + self.connected = False time.sleep(retry_sleep) retry_sleep *= 2 if retry_sleep > 60: @@ -189,7 +190,6 @@ def _redis_listen_with_retries(self): # pragma: no cover def _listen(self): # pragma: no cover channel = self.channel.encode('utf-8') - self.pubsub.subscribe(self.channel) for message in self._redis_listen_with_retries(): if message['channel'] == channel and \ message['type'] == 'message' and 'data' in message: diff --git a/tests/async/test_redis_manager.py b/tests/async/test_redis_manager.py index 01c0c375..046c26d2 100644 --- a/tests/async/test_redis_manager.py +++ b/tests/async/test_redis_manager.py @@ -12,7 +12,7 @@ def test_redis_not_installed(self): async_redis_manager.aioredis = None with pytest.raises(RuntimeError): - AsyncRedisManager('redis://') + AsyncRedisManager('redis://')._redis_connect() assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None async_redis_manager.aioredis = saved_redis @@ -22,7 +22,7 @@ def test_valkey_not_installed(self): async_redis_manager.aiovalkey = None with pytest.raises(RuntimeError): - AsyncRedisManager('valkey://') + AsyncRedisManager('valkey://')._redis_connect() assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None async_redis_manager.aiovalkey = saved_valkey @@ -34,18 +34,18 @@ def test_redis_valkey_not_installed(self): async_redis_manager.aiovalkey = None with pytest.raises(RuntimeError): - AsyncRedisManager('redis://') + AsyncRedisManager('redis://')._redis_connect() with pytest.raises(RuntimeError): - AsyncRedisManager('valkey://') + AsyncRedisManager('valkey://')._redis_connect() with pytest.raises(RuntimeError): - AsyncRedisManager('unix:///var/sock/redis.sock') + AsyncRedisManager('unix:///var/sock/redis.sock')._redis_connect() async_redis_manager.aioredis = saved_redis async_redis_manager.aiovalkey = saved_valkey def test_bad_url(self): with pytest.raises(ValueError): - AsyncRedisManager('http://localhost:6379') + AsyncRedisManager('http://localhost:6379')._redis_connect() def test_redis_connect(self): urls = [ @@ -72,6 +72,8 @@ def test_redis_connect(self): ] for url in urls: c = AsyncRedisManager(url) + assert c.redis is None + c._redis_connect() assert isinstance(c.redis, redis.asyncio.Redis) def test_valkey_connect(self): @@ -102,6 +104,8 @@ def test_valkey_connect(self): ] for url in urls: c = AsyncRedisManager(url) + assert c.redis is None + c._redis_connect() assert isinstance(c.redis, valkey.asyncio.Valkey) async_redis_manager.aioredis = saved_redis diff --git a/tests/common/test_redis_manager.py b/tests/common/test_redis_manager.py index 3beadf3b..dbc927e4 100644 --- a/tests/common/test_redis_manager.py +++ b/tests/common/test_redis_manager.py @@ -12,7 +12,7 @@ def test_redis_not_installed(self): redis_manager.redis = None with pytest.raises(RuntimeError): - RedisManager('redis://') + RedisManager('redis://')._redis_connect() assert RedisManager('unix:///var/sock/redis.sock') is not None redis_manager.redis = saved_redis @@ -22,7 +22,7 @@ def test_valkey_not_installed(self): redis_manager.valkey = None with pytest.raises(RuntimeError): - RedisManager('valkey://') + RedisManager('valkey://')._redis_connect() assert RedisManager('unix:///var/sock/redis.sock') is not None redis_manager.valkey = saved_valkey @@ -34,18 +34,18 @@ def test_redis_valkey_not_installed(self): redis_manager.valkey = None with pytest.raises(RuntimeError): - RedisManager('redis://') + RedisManager('redis://')._redis_connect() with pytest.raises(RuntimeError): - RedisManager('valkey://') + RedisManager('valkey://')._redis_connect() with pytest.raises(RuntimeError): - RedisManager('unix:///var/sock/redis.sock') + RedisManager('unix:///var/sock/redis.sock')._redis_connect() redis_manager.redis = saved_redis redis_manager.valkey = saved_valkey def test_bad_url(self): with pytest.raises(ValueError): - RedisManager('http://localhost:6379') + RedisManager('http://localhost:6379')._redis_connect() def test_redis_connect(self): urls = [ @@ -72,6 +72,8 @@ def test_redis_connect(self): ] for url in urls: c = RedisManager(url) + assert c.redis is None + c._redis_connect() assert isinstance(c.redis, redis.Redis) def test_valkey_connect(self): @@ -102,6 +104,8 @@ def test_valkey_connect(self): ] for url in urls: c = RedisManager(url) + assert c.redis is None + c._redis_connect() assert isinstance(c.redis, valkey.Valkey) redis_manager.redis = saved_redis From 5e898a9b93526e6e667767e54c60f4c84589989d Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 22 Nov 2025 18:15:59 +0000 Subject: [PATCH 158/173] Retry initial Redis connection (Fixes #1534) --- src/socketio/async_redis_manager.py | 7 ++++--- src/socketio/redis_manager.py | 7 ++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/socketio/async_redis_manager.py b/src/socketio/async_redis_manager.py index 1cb7fb9d..a43807ca 100644 --- a/src/socketio/async_redis_manager.py +++ b/src/socketio/async_redis_manager.py @@ -134,11 +134,12 @@ async def _publish(self, data): # pragma: no cover break async def _redis_listen_with_retries(self): # pragma: no cover - retry_sleep = 1 _, error = self._get_redis_module_and_error() + retry_sleep = 1 + subscribed = False while True: try: - if not self.connected: + if not subscribed: self._redis_connect() await self.pubsub.subscribe(self.channel) retry_sleep = 1 @@ -149,7 +150,7 @@ async def _redis_listen_with_retries(self): # pragma: no cover 'retrying in ' f'{retry_sleep} secs', extra={"redis_exception": str(exc)}) - self.connected = False + subscribed = False await asyncio.sleep(retry_sleep) retry_sleep *= 2 if retry_sleep > 60: diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index 827918b7..4a9d69d1 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -169,11 +169,12 @@ def _publish(self, data): # pragma: no cover break def _redis_listen_with_retries(self): # pragma: no cover - retry_sleep = 1 _, error = self._get_redis_module_and_error() + retry_sleep = 1 + subscribed = False while True: try: - if not self.connected: + if not subscribed: self._redis_connect() self.pubsub.subscribe(self.channel) retry_sleep = 1 @@ -182,7 +183,7 @@ def _redis_listen_with_retries(self): # pragma: no cover logger.error('Cannot receive from redis... ' f'retrying in {retry_sleep} secs', extra={"redis_exception": str(exc)}) - self.connected = False + subscribed = False time.sleep(retry_sleep) retry_sleep *= 2 if retry_sleep > 60: From a63007d17ad9beb6e4cd1da0d906ad9bea76d3ad Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 22 Nov 2025 18:38:45 +0000 Subject: [PATCH 159/173] Release 5.15.0 --- CHANGES.md | 9 +++++++++ pyproject.toml | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index fe2db445..b384da0e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,14 @@ # python-socketio change log +**Release 5.15.0** - 2025-11-22 + +- Retry initial Redis connection [#1534](https://github.com/miguelgrinberg/python-socketio/issues/1536) ([commit #1](https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad) [commit #2](https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d)) +- Correctly regenerate RabbitMQ binding after a connection failure [#1516](https://github.com/miguelgrinberg/python-socketio/issues/1516) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/c52e93b4a328d98a968bfbdec0cfd598b73ee913)) (thanks **Gritty_dev**!) +- Support `ext_type` in the `MsgPackPacket` class [#1521](https://github.com/miguelgrinberg/python-socketio/issues/1521) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/208925344a48485d2cd56e40eb74266c3bcb5311)) +- Support sending `bytesarray`s when using pub/sub managers ([commit](https://github.com/miguelgrinberg/python-socketio/commit/6c9b9974f72e2efdf62407ecab24ee6995448098)) +- Fix typos in documentation [#1520](https://github.com/miguelgrinberg/python-socketio/issues/1520) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/db3f1c2a0105c30cb833ddfca8f05fe4320468fd)) (thanks **Lê Nam Khánh**!) +- Improvements to the logging documentation ([commit](https://github.com/miguelgrinberg/python-socketio/commit/b423d0e38eef559b7e81acb7e32059de305f982c)) + **Release 5.14.3** - 2025-10-29 - Support Python's native `ConnectionRefusedError` exception to reject a connection [#1515](https://github.com/miguelgrinberg/python-socketio/issues/1515) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/f3b18bde3f16437b223491d4c3e440ea37105fe3)) diff --git a/pyproject.toml b/pyproject.toml index f444d74d..038fabb7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.14.4.dev0" +version = "5.15.0" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From 692dca4747798e81f4505d4c1cbfe3ab9490368f Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sat, 22 Nov 2025 18:50:22 +0000 Subject: [PATCH 160/173] Version 5.15.1.dev0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 038fabb7..a1c13910 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.15.0" +version = "5.15.1.dev0" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From 91553c1ac22b9a0cf7fe3d2205e0c011f8c8df12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Dec 2025 17:23:54 +0000 Subject: [PATCH 161/173] Bump django in /examples/server/wsgi/django_socketio (#1538) #nolog Bumps [django](https://github.com/django/django) from 4.2.26 to 4.2.27. - [Commits](https://github.com/django/django/compare/4.2.26...4.2.27) --- updated-dependencies: - dependency-name: django dependency-version: 4.2.27 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/wsgi/django_socketio/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/wsgi/django_socketio/requirements.txt b/examples/server/wsgi/django_socketio/requirements.txt index 065a4405..e2e8f50c 100644 --- a/examples/server/wsgi/django_socketio/requirements.txt +++ b/examples/server/wsgi/django_socketio/requirements.txt @@ -1,6 +1,6 @@ asgiref==3.6.0 bidict==0.22.1 -Django==4.2.26 +Django==4.2.27 gunicorn==23.0.0 h11==0.16.0 python-engineio From c279f26bb8c9887c4ca99d4d81ad331c4844438c Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Sun, 14 Dec 2025 19:35:44 +0000 Subject: [PATCH 162/173] Support multiple arguments via pubsub emits (Fixes #1539) (#1540) --- src/socketio/async_pubsub_manager.py | 9 ++ src/socketio/pubsub_manager.py | 9 ++ tests/async/test_pubsub_manager.py | 217 +++++++++++++++++++++++++-- tests/common/test_pubsub_manager.py | 207 +++++++++++++++++++++++-- 4 files changed, 422 insertions(+), 20 deletions(-) diff --git a/src/socketio/async_pubsub_manager.py b/src/socketio/async_pubsub_manager.py index 1bfcf9dc..6d631640 100644 --- a/src/socketio/async_pubsub_manager.py +++ b/src/socketio/async_pubsub_manager.py @@ -66,6 +66,10 @@ async def emit(self, event, data, namespace=None, room=None, skip_sid=None, callback = (room, namespace, id) else: callback = None + if isinstance(data, tuple): + data = list(data) + else: + data = [data] binary = Packet.data_is_binary(data) if binary: data, attachments = Packet.deconstruct_binary(data) @@ -155,6 +159,11 @@ async def _handle_emit(self, message): if message.get('binary'): attachments = [base64.b64decode(a) for a in data[1:]] data = Packet.reconstruct_binary(data[0], attachments) + if isinstance(data, list): + if len(data) == 1: + data = data[0] + else: + data = tuple(data) await super().emit(message['event'], data, namespace=message.get('namespace'), room=message.get('room'), diff --git a/src/socketio/pubsub_manager.py b/src/socketio/pubsub_manager.py index 3528d228..4254d4af 100644 --- a/src/socketio/pubsub_manager.py +++ b/src/socketio/pubsub_manager.py @@ -63,6 +63,10 @@ def emit(self, event, data, namespace=None, room=None, skip_sid=None, callback = (room, namespace, id) else: callback = None + if isinstance(data, tuple): + data = list(data) + else: + data = [data] binary = Packet.data_is_binary(data) if binary: data, attachments = Packet.deconstruct_binary(data) @@ -151,6 +155,11 @@ def _handle_emit(self, message): if message.get('binary'): attachments = [base64.b64decode(a) for a in data[1:]] data = Packet.reconstruct_binary(data[0], attachments) + if isinstance(data, list): + if len(data) == 1: + data = data[0] + else: + data = tuple(data) super().emit(message['event'], data, namespace=message.get('namespace'), room=message.get('room'), diff --git a/tests/async/test_pubsub_manager.py b/tests/async/test_pubsub_manager.py index f9cfb7eb..abf41a24 100644 --- a/tests/async/test_pubsub_manager.py +++ b/tests/async/test_pubsub_manager.py @@ -58,7 +58,7 @@ async def test_emit(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/', 'room': None, 'skip_sid': None, @@ -74,7 +74,7 @@ async def test_emit_binary(self): 'method': 'emit', 'event': 'foo', 'binary': True, - 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], + 'data': [[{'_placeholder': True, 'num': 0}], 'YmFy'], 'namespace': '/', 'room': None, 'skip_sid': None, @@ -88,7 +88,7 @@ async def test_emit_binary(self): 'method': 'emit', 'event': 'foo', 'binary': True, - 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], + 'data': [[{'foo': {'_placeholder': True, 'num': 0}}], 'YmFy'], 'namespace': '/', 'room': None, 'skip_sid': None, @@ -104,7 +104,7 @@ async def test_emit_bytearray(self): 'method': 'emit', 'event': 'foo', 'binary': True, - 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], + 'data': [[{'_placeholder': True, 'num': 0}], 'YmFy'], 'namespace': '/', 'room': None, 'skip_sid': None, @@ -118,7 +118,87 @@ async def test_emit_bytearray(self): 'method': 'emit', 'event': 'foo', 'binary': True, - 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], + 'data': [[{'foo': {'_placeholder': True, 'num': 0}}], 'YmFy'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + + async def test_emit_list(self): + await self.pm.emit('foo', [1, 'two']) + self.pm._publish.assert_awaited_once_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': False, + 'data': [[1, 'two']], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + await self.pm.emit('foo', [1, b'two', 'three']) + self.pm._publish.assert_awaited_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [ + [[1, {'_placeholder': True, 'num': 0}, 'three']], 'dHdv', + ], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + + async def test_emit_no_arguments(self): + await self.pm.emit('foo', ()) + self.pm._publish.assert_awaited_once_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': False, + 'data': [], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + + async def test_emit_multiple_arguments(self): + await self.pm.emit('foo', (1, 'two')) + self.pm._publish.assert_awaited_once_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': False, + 'data': [1, 'two'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + await self.pm.emit('foo', (1, b'two', 'three')) + self.pm._publish.assert_awaited_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [ + [1, {'_placeholder': True, 'num': 0}, 'three'], 'dHdv', + ], 'namespace': '/', 'room': None, 'skip_sid': None, @@ -135,7 +215,7 @@ async def test_emit_with_to(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/', 'room': sid, 'skip_sid': None, @@ -151,7 +231,7 @@ async def test_emit_with_namespace(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/baz', 'room': None, 'skip_sid': None, @@ -167,7 +247,7 @@ async def test_emit_with_room(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/', 'room': 'baz', 'skip_sid': None, @@ -183,7 +263,7 @@ async def test_emit_with_skip_sid(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/', 'room': None, 'skip_sid': 'baz', @@ -202,7 +282,7 @@ async def test_emit_with_callback(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/', 'room': 'baz', 'skip_sid': None, @@ -294,6 +374,20 @@ async def test_close_room_with_namespace(self): ) async def test_handle_emit(self): + with mock.patch.object( + async_manager.AsyncManager, 'emit' + ) as super_emit: + await self.pm._handle_emit({'event': 'foo', 'data': ['bar']}) + super_emit.assert_awaited_once_with( + 'foo', + 'bar', + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + + async def test_handle_legacy_emit(self): with mock.patch.object( async_manager.AsyncManager, 'emit' ) as super_emit: @@ -308,6 +402,37 @@ async def test_handle_emit(self): ) async def test_handle_emit_binary(self): + with mock.patch.object( + async_manager.AsyncManager, 'emit' + ) as super_emit: + await self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [[{'_placeholder': True, 'num': 0}], 'YmFy'], + }) + super_emit.assert_awaited_once_with( + 'foo', + b'bar', + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + await self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [[{'foo': {'_placeholder': True, 'num': 0}}], 'YmFy'], + }) + super_emit.assert_awaited_with( + 'foo', + {'foo': b'bar'}, + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + + async def test_handle_legacy_emit_binary(self): with mock.patch.object( async_manager.AsyncManager, 'emit' ) as super_emit: @@ -338,6 +463,78 @@ async def test_handle_emit_binary(self): callback=None, ) + async def test_handle_emit_list(self): + with mock.patch.object( + async_manager.AsyncManager, 'emit' + ) as super_emit: + await self.pm._handle_emit({'event': 'foo', 'data': [[1, 'two']]}) + super_emit.assert_awaited_once_with( + 'foo', + [1, 'two'], + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + await self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [ + [[1, {'_placeholder': True, 'num': 0}, 'three']], 'dHdv' + ] + }) + super_emit.assert_awaited_with( + 'foo', + [1, b'two', 'three'], + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + + async def test_handle_emit_no_arguments(self): + with mock.patch.object( + async_manager.AsyncManager, 'emit' + ) as super_emit: + await self.pm._handle_emit({'event': 'foo', 'data': []}) + super_emit.assert_awaited_once_with( + 'foo', + (), + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + + async def test_handle_emit_multiple_arguments(self): + with mock.patch.object( + async_manager.AsyncManager, 'emit' + ) as super_emit: + await self.pm._handle_emit({'event': 'foo', 'data': [1, 'two']}) + super_emit.assert_awaited_once_with( + 'foo', + (1, 'two'), + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + await self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [ + [1, {'_placeholder': True, 'num': 0}, 'three'], 'dHdv' + ] + }) + super_emit.assert_awaited_with( + 'foo', + (1, b'two', 'three'), + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + async def test_handle_emit_with_namespace(self): with mock.patch.object( async_manager.AsyncManager, 'emit' diff --git a/tests/common/test_pubsub_manager.py b/tests/common/test_pubsub_manager.py index abef2bf2..91b77854 100644 --- a/tests/common/test_pubsub_manager.py +++ b/tests/common/test_pubsub_manager.py @@ -70,7 +70,7 @@ def test_emit(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/', 'room': None, 'skip_sid': None, @@ -86,7 +86,7 @@ def test_emit_binary(self): 'method': 'emit', 'event': 'foo', 'binary': True, - 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], + 'data': [[{'_placeholder': True, 'num': 0}], 'YmFy'], 'namespace': '/', 'room': None, 'skip_sid': None, @@ -100,7 +100,7 @@ def test_emit_binary(self): 'method': 'emit', 'event': 'foo', 'binary': True, - 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], + 'data': [[{'foo': {'_placeholder': True, 'num': 0}}], 'YmFy'], 'namespace': '/', 'room': None, 'skip_sid': None, @@ -116,7 +116,7 @@ def test_emit_bytearray(self): 'method': 'emit', 'event': 'foo', 'binary': True, - 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], + 'data': [[{'_placeholder': True, 'num': 0}], 'YmFy'], 'namespace': '/', 'room': None, 'skip_sid': None, @@ -130,7 +130,87 @@ def test_emit_bytearray(self): 'method': 'emit', 'event': 'foo', 'binary': True, - 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], + 'data': [[{'foo': {'_placeholder': True, 'num': 0}}], 'YmFy'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + + def test_emit_list(self): + self.pm.emit('foo', [1, 'two']) + self.pm._publish.assert_called_once_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': False, + 'data': [[1, 'two']], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + self.pm.emit('foo', [1, b'two', 'three']) + self.pm._publish.assert_called_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [ + [[1, {'_placeholder': True, 'num': 0}, 'three']], 'dHdv', + ], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + + def test_emit_no_arguments(self): + self.pm.emit('foo', ()) + self.pm._publish.assert_called_once_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': False, + 'data': [], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + + def test_emit_multiple_arguments(self): + self.pm.emit('foo', (1, 'two')) + self.pm._publish.assert_called_once_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': False, + 'data': [1, 'two'], + 'namespace': '/', + 'room': None, + 'skip_sid': None, + 'callback': None, + 'host_id': '123456', + } + ) + self.pm.emit('foo', (1, b'two', 'three')) + self.pm._publish.assert_called_with( + { + 'method': 'emit', + 'event': 'foo', + 'binary': True, + 'data': [ + [1, {'_placeholder': True, 'num': 0}, 'three'], 'dHdv', + ], 'namespace': '/', 'room': None, 'skip_sid': None, @@ -147,7 +227,7 @@ def test_emit_with_to(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/', 'room': sid, 'skip_sid': None, @@ -163,7 +243,7 @@ def test_emit_with_namespace(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/baz', 'room': None, 'skip_sid': None, @@ -179,7 +259,7 @@ def test_emit_with_room(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/', 'room': 'baz', 'skip_sid': None, @@ -195,7 +275,7 @@ def test_emit_with_skip_sid(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/', 'room': None, 'skip_sid': 'baz', @@ -214,7 +294,7 @@ def test_emit_with_callback(self): 'method': 'emit', 'event': 'foo', 'binary': False, - 'data': 'bar', + 'data': ['bar'], 'namespace': '/', 'room': 'baz', 'skip_sid': None, @@ -305,6 +385,18 @@ def test_close_room_with_namespace(self): ) def test_handle_emit(self): + with mock.patch.object(manager.Manager, 'emit') as super_emit: + self.pm._handle_emit({'event': 'foo', 'data': ['bar']}) + super_emit.assert_called_once_with( + 'foo', + 'bar', + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + + def test_handle_legacy_emit(self): with mock.patch.object(manager.Manager, 'emit') as super_emit: self.pm._handle_emit({'event': 'foo', 'data': 'bar'}) super_emit.assert_called_once_with( @@ -317,6 +409,35 @@ def test_handle_emit(self): ) def test_handle_emit_binary(self): + with mock.patch.object(manager.Manager, 'emit') as super_emit: + self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [[{'_placeholder': True, 'num': 0}], 'YmFy'], + }) + super_emit.assert_called_once_with( + 'foo', + b'bar', + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [[{'foo': {'_placeholder': True, 'num': 0}}], 'YmFy'], + }) + super_emit.assert_called_with( + 'foo', + {'foo': b'bar'}, + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + + def test_handle_legacy_emit_binary(self): with mock.patch.object(manager.Manager, 'emit') as super_emit: self.pm._handle_emit({ 'event': 'foo', @@ -345,6 +466,72 @@ def test_handle_emit_binary(self): callback=None, ) + def test_handle_emit_list(self): + with mock.patch.object(manager.Manager, 'emit') as super_emit: + self.pm._handle_emit({'event': 'foo', 'data': [[1, 'two']]}) + super_emit.assert_called_once_with( + 'foo', + [1, 'two'], + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [ + [[1, {'_placeholder': True, 'num': 0}, 'three']], 'dHdv' + ], + }) + super_emit.assert_called_with( + 'foo', + [1, b'two', 'three'], + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + + def test_handle_emit_no_arguments(self): + with mock.patch.object(manager.Manager, 'emit') as super_emit: + self.pm._handle_emit({'event': 'foo', 'data': []}) + super_emit.assert_called_once_with( + 'foo', + (), + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + + def test_handle_emit_multiple_arguments(self): + with mock.patch.object(manager.Manager, 'emit') as super_emit: + self.pm._handle_emit({'event': 'foo', 'data': [1, 'two']}) + super_emit.assert_called_once_with( + 'foo', + (1, 'two'), + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + self.pm._handle_emit({ + 'event': 'foo', + 'binary': True, + 'data': [ + [1, {'_placeholder': True, 'num': 0}, 'three'], 'dHdv' + ], + }) + super_emit.assert_called_with( + 'foo', + (1, b'two', 'three'), + namespace=None, + room=None, + skip_sid=None, + callback=None, + ) + def test_handle_emit_with_namespace(self): with mock.patch.object(manager.Manager, 'emit') as super_emit: self.pm._handle_emit( From c6eaca07bb70b42dc6dea5f03628a8df3b5a9ff7 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 16 Dec 2025 23:42:01 +0000 Subject: [PATCH 163/173] Release 5.15.1 --- CHANGES.md | 4 ++++ pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index b384da0e..2ec98f1c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,9 @@ # python-socketio change log +**Release 5.15.1** - 2025-12-16 + +- Restore support multiple arguments via pubsub emits [#1540](https://github.com/miguelgrinberg/python-socketio/issues/1540) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/c279f26bb8c9887c4ca99d4d81ad331c4844438c)) + **Release 5.15.0** - 2025-11-22 - Retry initial Redis connection [#1534](https://github.com/miguelgrinberg/python-socketio/issues/1536) ([commit #1](https://github.com/miguelgrinberg/python-socketio/commit/1e903e173a2d7b04599c4f7f9630c1abbb531fad) [commit #2](https://github.com/miguelgrinberg/python-socketio/commit/5e898a9b93526e6e667767e54c60f4c84589989d)) diff --git a/pyproject.toml b/pyproject.toml index a1c13910..a8f1833e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.15.1.dev0" +version = "5.15.1" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From 412a6cf84960649d26d11f4aa33176bd94dbc213 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 16 Dec 2025 23:48:42 +0000 Subject: [PATCH 164/173] Version 5.15.2.dev0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index a8f1833e..ff9e27d7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.15.1" +version = "5.15.2.dev0" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From b235699d9b06564753c570b76055997e9d62a938 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 24 Dec 2025 23:40:33 +0000 Subject: [PATCH 165/173] Address deprecations --- src/socketio/async_admin.py | 3 ++- src/socketio/async_client.py | 7 ++++--- src/socketio/async_manager.py | 3 ++- src/socketio/async_namespace.py | 5 +++-- src/socketio/async_server.py | 3 ++- 5 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/socketio/async_admin.py b/src/socketio/async_admin.py index b052d8fe..b426b71e 100644 --- a/src/socketio/async_admin.py +++ b/src/socketio/async_admin.py @@ -1,6 +1,7 @@ import asyncio from datetime import datetime, timezone import functools +import inspect import os import socket import time @@ -124,7 +125,7 @@ async def admin_connect(self, sid, environ, client_auth): elif isinstance(self.auth, list): authenticated = client_auth in self.auth else: - if asyncio.iscoroutinefunction(self.auth): + if inspect.iscoroutinefunction(self.auth): authenticated = await self.auth(client_auth) else: authenticated = self.auth(client_auth) diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index ead963d0..49241ed0 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -1,4 +1,5 @@ import asyncio +import inspect import logging import random @@ -375,7 +376,7 @@ async def _get_real_value(self, value): callables.""" if not callable(value): return value - if asyncio.iscoroutinefunction(value): + if inspect.iscoroutinefunction(value): return await value() return value() @@ -437,7 +438,7 @@ async def _handle_ack(self, namespace, id, data): else: del self.callbacks[namespace][id] if callback is not None: - if asyncio.iscoroutinefunction(callback): + if inspect.iscoroutinefunction(callback): await callback(*data) else: callback(*data) @@ -464,7 +465,7 @@ async def _trigger_event(self, event, namespace, *args): # first see if we have an explicit handler for the event handler, args = self._get_event_handler(event, namespace, args) if handler: - if asyncio.iscoroutinefunction(handler): + if inspect.iscoroutinefunction(handler): try: try: ret = await handler(*args) diff --git a/src/socketio/async_manager.py b/src/socketio/async_manager.py index 47e7a79f..19028b04 100644 --- a/src/socketio/async_manager.py +++ b/src/socketio/async_manager.py @@ -1,4 +1,5 @@ import asyncio +import inspect from engineio import packet as eio_packet from socketio import packet @@ -113,7 +114,7 @@ async def trigger_callback(self, sid, id, data): del self.callbacks[sid][id] if callback is not None: ret = callback(*data) - if asyncio.iscoroutine(ret): + if inspect.iscoroutine(ret): try: await ret except asyncio.CancelledError: # pragma: no cover diff --git a/src/socketio/async_namespace.py b/src/socketio/async_namespace.py index 42d65089..4035e336 100644 --- a/src/socketio/async_namespace.py +++ b/src/socketio/async_namespace.py @@ -1,4 +1,5 @@ import asyncio +import inspect from socketio import base_namespace @@ -32,7 +33,7 @@ async def trigger_event(self, event, *args): handler_name = 'on_' + (event or '') if hasattr(self, handler_name): handler = getattr(self, handler_name) - if asyncio.iscoroutinefunction(handler) is True: + if inspect.iscoroutinefunction(handler) is True: try: try: ret = await handler(*args) @@ -213,7 +214,7 @@ async def trigger_event(self, event, *args): handler_name = 'on_' + (event or '') if hasattr(self, handler_name): handler = getattr(self, handler_name) - if asyncio.iscoroutinefunction(handler) is True: + if inspect.iscoroutinefunction(handler) is True: try: try: ret = await handler(*args) diff --git a/src/socketio/async_server.py b/src/socketio/async_server.py index 6c9e3ca3..e13009ef 100644 --- a/src/socketio/async_server.py +++ b/src/socketio/async_server.py @@ -1,4 +1,5 @@ import asyncio +import inspect import engineio @@ -637,7 +638,7 @@ async def _trigger_event(self, event, namespace, *args): # first see if we have an explicit handler for the event handler, args = self._get_event_handler(event, namespace, args) if handler: - if asyncio.iscoroutinefunction(handler): + if inspect.iscoroutinefunction(handler): try: try: ret = await handler(*args) From d0728d2f74538762dd551fa9cd0cd1fd5aedfa37 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 24 Dec 2025 23:40:42 +0000 Subject: [PATCH 166/173] Drop Python 3.8 and 3.9 from CI --- .github/workflows/tests.yml | 2 +- tox.ini | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6a8433e4..99ad78e2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -16,7 +16,7 @@ jobs: strategy: matrix: os: [windows-latest, macos-latest, ubuntu-latest] - python: ['3.8', '3.9', '3.10', '3.11', '3.12', '3.13', '3.14', 'pypy-3.11'] + python: ['3.10', '3.11', '3.12', '3.13', '3.14', 'pypy-3.11'] fail-fast: false runs-on: ${{ matrix.os }} steps: diff --git a/tox.ini b/tox.ini index b73e4bbc..02297a2c 100644 --- a/tox.ini +++ b/tox.ini @@ -1,11 +1,9 @@ [tox] -envlist=flake8,py{38,39,310,311,312,313,314},docs +envlist=flake8,py{310,311,312,313,314},docs skip_missing_interpreters=True [gh-actions] python = - 3.8: py38 - 3.9: py39 3.10: py310 3.11: py311 3.12: py312 From 9e624b38c863efaacd104f2c0dd7125e12c8cac2 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 24 Dec 2025 23:51:21 +0000 Subject: [PATCH 167/173] Release 5.16.0 --- CHANGES.md | 5 +++++ pyproject.toml | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 2ec98f1c..7e97e423 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,10 @@ # python-socketio change log +**Release 5.16.0** - 2025-12-24 + +- Address deprecation warnings ([commit](https://github.com/miguelgrinberg/python-socketio/commit/b235699d9b06564753c570b76055997e9d62a938)) +- Drop Python 3.8 and 3.9 from CI builds ([commit](https://github.com/miguelgrinberg/python-socketio/commit/d0728d2f74538762dd551fa9cd0cd1fd5aedfa37)) + **Release 5.15.1** - 2025-12-16 - Restore support multiple arguments via pubsub emits [#1540](https://github.com/miguelgrinberg/python-socketio/issues/1540) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/c279f26bb8c9887c4ca99d4d81ad331c4844438c)) diff --git a/pyproject.toml b/pyproject.toml index ff9e27d7..21f1813e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.15.2.dev0" +version = "5.16.0" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From 2ce73481986061c54c702b89a511df32e16aa57f Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Wed, 24 Dec 2025 23:51:50 +0000 Subject: [PATCH 168/173] Version 5.16.1.dev0 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 21f1813e..e1861764 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "python-socketio" -version = "5.16.0" +version = "5.16.1.dev0" license = {text = "MIT"} authors = [ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, From e01550bda0d8963540a5bac28db71edb6e57e542 Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Tue, 30 Dec 2025 19:47:12 +0000 Subject: [PATCH 169/173] Fix typos in documentation #nolog (Fixes #1541) --- docs/client.rst | 2 +- docs/server.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/client.rst b/docs/client.rst index 64cbec88..1e23c749 100644 --- a/docs/client.rst +++ b/docs/client.rst @@ -280,7 +280,7 @@ remaining arguments are the same as for a regular event handler. The ``connect`` and ``disconnect`` events have to be defined explicitly and are not invoked on a catch-all event handler. -Similarily, a "catch-all" namespace handler is invoked for any connected +Similarly, a "catch-all" namespace handler is invoked for any connected namespaces that do not have an explicitly defined event handler. As with catch-all events, ``'*'`` is used in place of a namespace:: diff --git a/docs/server.rst b/docs/server.rst index f31e6680..e73f14c5 100644 --- a/docs/server.rst +++ b/docs/server.rst @@ -492,7 +492,7 @@ information. Catch-All Namespaces ~~~~~~~~~~~~~~~~~~~~ -Similarily to catch-all event handlers, a "catch-all" namespace can be used +Similarly to catch-all event handlers, a "catch-all" namespace can be used when defining event handlers for any connected namespaces that do not have an explicitly defined event handler. As with catch-all events, ``'*'`` is used in place of a namespace:: From 7353b276137ec00723f747edf6d11829b85fb4e8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 1 Jan 2026 14:40:06 +0000 Subject: [PATCH 170/173] Bump qs and express in /examples/server/javascript (#1542) #nolog Bumps [qs](https://github.com/ljharb/qs) to 6.14.1 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `qs` from 6.13.0 to 6.14.1 - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.14.1) Updates `express` from 4.21.2 to 4.22.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.2...v4.22.1) --- updated-dependencies: - dependency-name: qs dependency-version: 6.14.1 dependency-type: indirect - dependency-name: express dependency-version: 4.22.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/javascript/package-lock.json | 482 ++++++++++++------- examples/server/javascript/package.json | 2 +- 2 files changed, 296 insertions(+), 188 deletions(-) diff --git a/examples/server/javascript/package-lock.json b/examples/server/javascript/package-lock.json index 4bae767a..7a3ed280 100644 --- a/examples/server/javascript/package-lock.json +++ b/examples/server/javascript/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0", "dependencies": { "@socket.io/admin-ui": "^0.5.1", - "express": "^4.21.2", + "express": "^4.22.1", "smoothie": "1.19.0", "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" @@ -133,16 +133,25 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -211,22 +220,6 @@ } } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -244,6 +237,19 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -306,12 +312,9 @@ } }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "engines": { "node": ">= 0.4" } @@ -324,6 +327,17 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -338,38 +352,38 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -403,6 +417,20 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/express/node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -466,15 +494,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -483,32 +516,22 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { + "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dependencies": { - "es-define-property": "^1.0.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "engines": { "node": ">= 0.4" }, @@ -517,9 +540,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -577,6 +600,14 @@ "node": ">= 0.10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -653,9 +684,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "engines": { "node": ">= 0.4" }, @@ -822,36 +853,71 @@ "node": ">= 0.8" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dependencies": { - "define-data-property": "^1.1.4", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -1108,16 +1174,22 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "requires": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" } }, "content-disposition": { @@ -1160,16 +1232,6 @@ "ms": "2.1.2" } }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1180,6 +1242,16 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1232,18 +1304,23 @@ "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==" }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "requires": { - "get-intrinsic": "^1.2.4" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, "es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1255,38 +1332,38 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1309,6 +1386,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "requires": { + "side-channel": "^1.1.0" + } } } }, @@ -1362,42 +1447,40 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" } }, - "gopd": { + "get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "requires": { - "es-define-property": "^1.0.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" } }, - "has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, "hasown": { "version": "2.0.2", @@ -1437,6 +1520,11 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1486,9 +1574,9 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, "on-finished": { "version": "2.4.1", @@ -1611,33 +1699,53 @@ } } }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "requires": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" } }, "smoothie": { diff --git a/examples/server/javascript/package.json b/examples/server/javascript/package.json index 1e0e150c..84715872 100644 --- a/examples/server/javascript/package.json +++ b/examples/server/javascript/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "dependencies": { "@socket.io/admin-ui": "^0.5.1", - "express": "^4.21.2", + "express": "^4.22.1", "smoothie": "1.19.0", "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" From 174629eb8d49031eb579f842c9b9575eb09b22c2 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:44:41 +0000 Subject: [PATCH 171/173] Bump aiohttp from 3.12.14 to 3.13.3 in /examples/server/aiohttp (#1543) #nolog --- updated-dependencies: - dependency-name: aiohttp dependency-version: 3.13.3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/server/aiohttp/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/server/aiohttp/requirements.txt b/examples/server/aiohttp/requirements.txt index aec8bf98..f1a60ed0 100644 --- a/examples/server/aiohttp/requirements.txt +++ b/examples/server/aiohttp/requirements.txt @@ -1,4 +1,4 @@ -aiohttp==3.12.14 +aiohttp==3.13.3 async-timeout==1.1.0 chardet==2.3.0 multidict==2.1.4 From 8469b69261eefc3c1cb2d08473fd255cd23fc8d5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 10:00:25 +0000 Subject: [PATCH 172/173] Bump qs and express in /examples/client/javascript (#1544) #nolog Bumps [qs](https://github.com/ljharb/qs) to 6.14.1 and updates ancestor dependency [express](https://github.com/expressjs/express). These dependencies need to be updated together. Updates `qs` from 6.13.0 to 6.14.1 - [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md) - [Commits](https://github.com/ljharb/qs/compare/v6.13.0...v6.14.1) Updates `express` from 4.21.2 to 4.22.1 - [Release notes](https://github.com/expressjs/express/releases) - [Changelog](https://github.com/expressjs/express/blob/v4.22.1/History.md) - [Commits](https://github.com/expressjs/express/compare/4.21.2...v4.22.1) --- updated-dependencies: - dependency-name: qs dependency-version: 6.14.1 dependency-type: indirect - dependency-name: express dependency-version: 4.22.1 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- examples/client/javascript/package-lock.json | 482 ++++++++++++------- examples/client/javascript/package.json | 2 +- 2 files changed, 296 insertions(+), 188 deletions(-) diff --git a/examples/client/javascript/package-lock.json b/examples/client/javascript/package-lock.json index 5a2ec0ca..fef1fa8f 100644 --- a/examples/client/javascript/package-lock.json +++ b/examples/client/javascript/package-lock.json @@ -8,7 +8,7 @@ "name": "socketio-examples", "version": "0.1.0", "dependencies": { - "express": "^4.21.2", + "express": "^4.22.1", "smoothie": "1.19.0", "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" @@ -96,16 +96,25 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dependencies": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { "node": ">= 0.4" @@ -166,22 +175,6 @@ "ms": "2.0.0" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -199,6 +192,19 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -303,12 +309,9 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "engines": { "node": ">= 0.4" } @@ -321,6 +324,17 @@ "node": ">= 0.4" } }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -335,38 +349,38 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -387,6 +401,20 @@ "node": ">= 0.8" } }, + "node_modules/express/node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/finalhandler": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", @@ -437,15 +465,20 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -454,32 +487,22 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gopd": { + "node_modules/get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dependencies": { - "es-define-property": "^1.0.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">= 0.4" } }, - "node_modules/has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "engines": { "node": ">= 0.4" }, @@ -488,9 +511,9 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "engines": { "node": ">= 0.4" }, @@ -548,6 +571,14 @@ "node": ">= 0.10" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -624,9 +655,9 @@ } }, "node_modules/object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "engines": { "node": ">= 0.4" }, @@ -780,36 +811,71 @@ "node": ">= 0.8" } }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dependencies": { - "define-data-property": "^1.1.4", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -1115,16 +1181,22 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "requires": { - "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" + "function-bind": "^1.1.2" + } + }, + "call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" } }, "content-disposition": { @@ -1167,16 +1239,6 @@ "ms": "2.0.0" } }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, "depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1187,6 +1249,16 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==" }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -1267,18 +1339,23 @@ "integrity": "sha512-9JktcM3u18nU9N2Lz3bWeBgxVgOKpw7yhRaoxQA3FUDZzzw+9WlA6p4G4u0RixNkg14fH7EfEc/RhpurtiROTQ==" }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "requires": { - "get-intrinsic": "^1.2.4" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, "es-errors": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, + "es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "requires": { + "es-errors": "^1.3.0" + } + }, "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1290,38 +1367,38 @@ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" }, "express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -1331,6 +1408,14 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "requires": { + "side-channel": "^1.1.0" + } } } }, @@ -1371,42 +1456,40 @@ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" } }, - "gopd": { + "get-proto": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } - }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "requires": { - "es-define-property": "^1.0.0" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" } }, - "has-proto": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", - "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, "hasown": { "version": "2.0.2", @@ -1446,6 +1529,11 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -1495,9 +1583,9 @@ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, "on-finished": { "version": "2.4.1", @@ -1605,33 +1693,53 @@ } } }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - } - }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + } + }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "requires": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" } }, "smoothie": { diff --git a/examples/client/javascript/package.json b/examples/client/javascript/package.json index 6bea0d87..8f1ec830 100644 --- a/examples/client/javascript/package.json +++ b/examples/client/javascript/package.json @@ -2,7 +2,7 @@ "name": "socketio-examples", "version": "0.1.0", "dependencies": { - "express": "^4.21.2", + "express": "^4.22.1", "smoothie": "1.19.0", "socket.io": "^4.8.0", "socket.io-client": "^4.6.1" From 1c2eab13a92fac9e43663eb0b5f099eb1c40ea5b Mon Sep 17 00:00:00 2001 From: Miguel Grinberg Date: Thu, 8 Jan 2026 19:52:30 +0000 Subject: [PATCH 173/173] Admin UI fixes: remove duplicate tasks, report transport upgrades --- src/socketio/admin.py | 6 ++++++ src/socketio/async_admin.py | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/socketio/admin.py b/src/socketio/admin.py index 12b905ea..e59da8ab 100644 --- a/src/socketio/admin.py +++ b/src/socketio/admin.py @@ -194,6 +194,8 @@ def shutdown(self): if self.stats_task: # pragma: no branch self.stop_stats_event.set() self.stats_task.join() + self.stop_stats_event.clear() + self.stats_task = None def _trigger_event(self, event, namespace, *args): t = time.time() @@ -206,6 +208,9 @@ def _trigger_event(self, event, namespace, *args): serialized_socket, datetime.fromtimestamp(t, timezone.utc).isoformat(), ), namespace=self.admin_namespace) + if not self.sio.eio._get_socket(eio_sid).upgraded: + self.sio.start_background_task( + self._check_for_upgrade, eio_sid, sid, namespace) elif event == 'disconnect': del self.sio.manager._timestamps[sid] reason = args[1] @@ -283,6 +288,7 @@ def _emit(self, event, data, namespace, room=None, skip_sid=None, def _handle_eio_connect(self, eio_sid, environ): if self.stop_stats_event is None: self.stop_stats_event = self.sio.eio.create_event() + if self.stats_task is None: self.stats_task = self.sio.start_background_task( self._emit_server_stats) diff --git a/src/socketio/async_admin.py b/src/socketio/async_admin.py index b426b71e..9d0fc9f3 100644 --- a/src/socketio/async_admin.py +++ b/src/socketio/async_admin.py @@ -157,9 +157,6 @@ async def config(sid): namespace=self.admin_namespace) self.sio.start_background_task(config, sid) - self.stop_stats_event = self.sio.eio.create_event() - self.stats_task = self.sio.start_background_task( - self._emit_server_stats) async def admin_emit(self, _, namespace, room_filter, event, *data): await self.sio.emit(event, data, to=room_filter, namespace=namespace) @@ -183,6 +180,8 @@ async def shutdown(self): if self.stats_task: # pragma: no branch self.stop_stats_event.set() await asyncio.gather(self.stats_task) + self.stats_task = None + self.stop_stats_event.clear() async def _trigger_event(self, event, namespace, *args): t = time.time() @@ -195,6 +194,9 @@ async def _trigger_event(self, event, namespace, *args): serialized_socket, datetime.fromtimestamp(t, timezone.utc).isoformat(), ), namespace=self.admin_namespace) + if not self.sio.eio._get_socket(eio_sid).upgraded: + self.sio.start_background_task( + self._check_for_upgrade, eio_sid, sid, namespace) elif event == 'disconnect': del self.sio.manager._timestamps[sid] reason = args[1] @@ -273,6 +275,7 @@ async def _emit(self, event, data, namespace, room=None, skip_sid=None, async def _handle_eio_connect(self, eio_sid, environ): if self.stop_stats_event is None: self.stop_stats_event = self.sio.eio.create_event() + if self.stats_task is None: self.stats_task = self.sio.start_background_task( self._emit_server_stats)