diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..b3804da --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,16 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.8.0 + hooks: + - id: ruff + args: ["--fix"] + - id: ruff-format + +# - repo: https://github.com/pre-commit/mirrors-mypy +# rev: v1.13.0 +# hooks: +# - id: mypy +# additional_dependencies: +# - pydantic==2.9.2 +# - redis==5.0.7 +# args: [] diff --git a/docs/conf.py b/docs/conf.py index b5d242b..a42f667 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # Configuration file for the Sphinx documentation builder. # @@ -19,14 +18,14 @@ # -- Project information ----------------------------------------------------- -project = 'python-socketio' -copyright = '2018, Miguel Grinberg' -author = 'Miguel Grinberg' +project = "python-socketio" +copyright = "2018, Miguel Grinberg" +author = "Miguel Grinberg" # The short X.Y version -version = '' +version = "" # The full version, including alpha/beta/rc tags -release = '' +release = "" # -- General configuration --------------------------------------------------- @@ -39,34 +38,34 @@ release = '' # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', + "sphinx.ext.autodoc", ] -autodoc_member_order = 'alphabetical' +autodoc_member_order = "alphabetical" # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # # This is also used if you do content translation via gettext catalogs. # Usually you set "language" from the command line for these cases. -language = 'en' +language = "en" # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] # The name of the Pygments (syntax highlighting) style to use. pygments_style = None @@ -77,26 +76,25 @@ pygments_style = None # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { - 'github_user': 'miguelgrinberg', - 'github_repo': 'python-socketio', - 'github_banner': True, - 'github_button': True, - 'github_type': 'star', - 'fixed_sidebar': True, - + "github_user": "miguelgrinberg", + "github_repo": "python-socketio", + "github_banner": True, + "github_button": True, + "github_type": "star", + "fixed_sidebar": True, } # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # Custom sidebar templates, must be a dictionary that maps document names # to template names. @@ -112,7 +110,7 @@ html_static_path = ['_static'] # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'python-socketiodoc' +htmlhelp_basename = "python-socketiodoc" # -- Options for LaTeX output ------------------------------------------------ @@ -121,15 +119,12 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -139,8 +134,13 @@ latex_elements = { # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'python-socketio.tex', 'python-socketio Documentation', - 'Miguel Grinberg', 'manual'), + ( + master_doc, + "python-socketio.tex", + "python-socketio Documentation", + "Miguel Grinberg", + "manual", + ), ] @@ -149,8 +149,7 @@ latex_documents = [ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'python-socketio', 'python-socketio Documentation', - [author], 1) + (master_doc, "python-socketio", "python-socketio Documentation", [author], 1) ] @@ -160,9 +159,15 @@ man_pages = [ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'python-socketio', 'python-socketio Documentation', - author, 'python-socketio', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "python-socketio", + "python-socketio Documentation", + author, + "python-socketio", + "One line description of project.", + "Miscellaneous", + ), ] @@ -181,7 +186,7 @@ epub_title = project # epub_uid = '' # A list of files that should not be packed into the epub file. -epub_exclude_files = ['search.html'] +epub_exclude_files = ["search.html"] # -- Extension configuration ------------------------------------------------- diff --git a/examples/client/async/fiddle_client.py b/examples/client/async/fiddle_client.py index e5aeb6c..00c57d3 100644 --- a/examples/client/async/fiddle_client.py +++ b/examples/client/async/fiddle_client.py @@ -1,4 +1,5 @@ import asyncio + import socketio sio = socketio.AsyncClient() @@ -6,12 +7,12 @@ sio = socketio.AsyncClient() @sio.event async def connect(): - print('connected to server') + print("connected to server") @sio.event async def disconnect(reason): - print('disconnected from server, reason:', reason) + print("disconnected from server, reason:", reason) @sio.event @@ -20,9 +21,9 @@ def hello(a, b, c): async def start_server(): - await sio.connect('http://localhost:5000', auth={'token': 'my-token'}) + await sio.connect("http://localhost:5000", auth={"token": "my-token"}) await sio.wait() -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(start_server()) diff --git a/examples/client/async/latency_client.py b/examples/client/async/latency_client.py index 57be604..ff67020 100644 --- a/examples/client/async/latency_client.py +++ b/examples/client/async/latency_client.py @@ -1,5 +1,6 @@ import asyncio import time + import socketio loop = asyncio.get_event_loop() @@ -10,28 +11,28 @@ start_timer = None async def send_ping(): global start_timer start_timer = time.time() - await sio.emit('ping_from_client') + await sio.emit("ping_from_client") @sio.event async def connect(): - print('connected to server') + print("connected to server") await send_ping() @sio.event async def pong_from_server(): latency = time.time() - start_timer - print(f'latency is {latency * 1000:.2f} ms') + print(f"latency is {latency * 1000:.2f} ms") await sio.sleep(1) if sio.connected: await send_ping() async def start_server(): - await sio.connect('http://localhost:5000') + await sio.connect("http://localhost:5000") await sio.wait() -if __name__ == '__main__': +if __name__ == "__main__": loop.run_until_complete(start_server()) diff --git a/examples/client/sync/fiddle_client.py b/examples/client/sync/fiddle_client.py index 71a7a54..323937a 100644 --- a/examples/client/sync/fiddle_client.py +++ b/examples/client/sync/fiddle_client.py @@ -5,12 +5,12 @@ sio = socketio.Client() @sio.event def connect(): - print('connected to server') + print("connected to server") @sio.event def disconnect(reason): - print('disconnected from server, reason:', reason) + print("disconnected from server, reason:", reason) @sio.event @@ -18,6 +18,6 @@ def hello(a, b, c): print(a, b, c) -if __name__ == '__main__': - sio.connect('http://localhost:5000', auth={'token': 'my-token'}) +if __name__ == "__main__": + sio.connect("http://localhost:5000", auth={"token": "my-token"}) sio.wait() diff --git a/examples/client/sync/latency_client.py b/examples/client/sync/latency_client.py index 94dcec9..6dcda42 100644 --- a/examples/client/sync/latency_client.py +++ b/examples/client/sync/latency_client.py @@ -1,4 +1,5 @@ import time + import socketio sio = socketio.Client(logger=True, engineio_logger=True) @@ -8,24 +9,24 @@ start_timer = None def send_ping(): global start_timer start_timer = time.time() - sio.emit('ping_from_client') + sio.emit("ping_from_client") @sio.event def connect(): - print('connected to server') + print("connected to server") send_ping() @sio.event def pong_from_server(): latency = time.time() - start_timer - print(f'latency is {latency * 1000:.2f} ms') + print(f"latency is {latency * 1000:.2f} ms") sio.sleep(1) if sio.connected: send_ping() -if __name__ == '__main__': - sio.connect('http://localhost:5000') +if __name__ == "__main__": + sio.connect("http://localhost:5000") sio.wait() diff --git a/examples/server/aiohttp/app.py b/examples/server/aiohttp/app.py index 1568ca1..34a538c 100644 --- a/examples/server/aiohttp/app.py +++ b/examples/server/aiohttp/app.py @@ -2,7 +2,7 @@ from aiohttp import web import socketio -sio = socketio.AsyncServer(async_mode='aiohttp') +sio = socketio.AsyncServer(async_mode="aiohttp") app = web.Application() sio.attach(app) @@ -13,50 +13,51 @@ async def background_task(): while True: await sio.sleep(10) count += 1 - await sio.emit('my_response', {'data': 'Server generated event'}) + await sio.emit("my_response", {"data": "Server generated event"}) async def index(request): - with open('app.html') as f: - return web.Response(text=f.read(), content_type='text/html') + with open("app.html") as f: + return web.Response(text=f.read(), content_type="text/html") @sio.event async def my_event(sid, message): - await sio.emit('my_response', {'data': message['data']}, room=sid) + await sio.emit("my_response", {"data": message["data"]}, room=sid) @sio.event async def my_broadcast_event(sid, message): - await sio.emit('my_response', {'data': message['data']}) + await sio.emit("my_response", {"data": message["data"]}) @sio.event async def join(sid, message): - await sio.enter_room(sid, message['room']) - await sio.emit('my_response', {'data': 'Entered room: ' + message['room']}, - room=sid) + await sio.enter_room(sid, message["room"]) + await sio.emit( + "my_response", {"data": "Entered room: " + message["room"]}, room=sid + ) @sio.event async def leave(sid, message): - await sio.leave_room(sid, message['room']) - await sio.emit('my_response', {'data': 'Left room: ' + message['room']}, - room=sid) + await sio.leave_room(sid, message["room"]) + await sio.emit("my_response", {"data": "Left room: " + message["room"]}, room=sid) @sio.event async def close_room(sid, message): - await sio.emit('my_response', - {'data': 'Room ' + message['room'] + ' is closing.'}, - room=message['room']) - await sio.close_room(message['room']) + await sio.emit( + "my_response", + {"data": "Room " + message["room"] + " is closing."}, + room=message["room"], + ) + await sio.close_room(message["room"]) @sio.event async def my_room_event(sid, message): - await sio.emit('my_response', {'data': message['data']}, - room=message['room']) + await sio.emit("my_response", {"data": message["data"]}, room=message["room"]) @sio.event @@ -66,16 +67,16 @@ async def disconnect_request(sid): @sio.event async def connect(sid, environ): - await sio.emit('my_response', {'data': 'Connected', 'count': 0}, room=sid) + await sio.emit("my_response", {"data": "Connected", "count": 0}, room=sid) @sio.event def disconnect(sid, reason): - print('Client disconnected, reason:', reason) + print("Client disconnected, reason:", reason) -app.router.add_static('/static', 'static') -app.router.add_get('/', index) +app.router.add_static("/static", "static") +app.router.add_get("/", index) async def init_app(): @@ -83,5 +84,5 @@ async def init_app(): return app -if __name__ == '__main__': +if __name__ == "__main__": web.run_app(init_app(), port=5000) diff --git a/examples/server/aiohttp/fiddle.py b/examples/server/aiohttp/fiddle.py index 64ce330..2ce071d 100644 --- a/examples/server/aiohttp/fiddle.py +++ b/examples/server/aiohttp/fiddle.py @@ -2,30 +2,30 @@ from aiohttp import web import socketio -sio = socketio.AsyncServer(async_mode='aiohttp') +sio = socketio.AsyncServer(async_mode="aiohttp") app = web.Application() sio.attach(app) async def index(request): - with open('fiddle.html') as f: - return web.Response(text=f.read(), content_type='text/html') + with open("fiddle.html") as f: + return web.Response(text=f.read(), content_type="text/html") @sio.event async def connect(sid, environ, auth): - print(f'connected auth={auth} sid={sid}') - await sio.emit('hello', (1, 2, {'hello': 'you'}), to=sid) + print(f"connected auth={auth} sid={sid}") + await sio.emit("hello", (1, 2, {"hello": "you"}), to=sid) @sio.event def disconnect(sid, reason): - print('disconnected', sid, reason) + print("disconnected", sid, reason) -app.router.add_static('/static', 'static') -app.router.add_get('/', index) +app.router.add_static("/static", "static") +app.router.add_get("/", index) -if __name__ == '__main__': +if __name__ == "__main__": web.run_app(app, port=5000) diff --git a/examples/server/aiohttp/latency.py b/examples/server/aiohttp/latency.py index 7e1f03c..ea11f0c 100644 --- a/examples/server/aiohttp/latency.py +++ b/examples/server/aiohttp/latency.py @@ -2,24 +2,24 @@ from aiohttp import web import socketio -sio = socketio.AsyncServer(async_mode='aiohttp') +sio = socketio.AsyncServer(async_mode="aiohttp") app = web.Application() sio.attach(app) async def index(request): - with open('latency.html') as f: - return web.Response(text=f.read(), content_type='text/html') + with open("latency.html") as f: + return web.Response(text=f.read(), content_type="text/html") @sio.event async def ping_from_client(sid): - await sio.emit('pong_from_server', room=sid) + await sio.emit("pong_from_server", room=sid) -app.router.add_static('/static', 'static') -app.router.add_get('/', index) +app.router.add_static("/static", "static") +app.router.add_get("/", index) -if __name__ == '__main__': +if __name__ == "__main__": web.run_app(app) diff --git a/examples/server/asgi/app.py b/examples/server/asgi/app.py index 996dc27..ae252f1 100644 --- a/examples/server/asgi/app.py +++ b/examples/server/asgi/app.py @@ -4,25 +4,32 @@ # Admin UI hosted at https://admin.socket.io instrument = True admin_login = { - 'username': 'admin', - 'password': 'python', # change this to a strong secret for production use! + "username": "admin", + "password": "python", # change this to a strong secret for production use! } import uvicorn + import socketio 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 - ]) + 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', -}) +app = socketio.ASGIApp( + sio, + static_files={ + "/": "app.html", + }, +) background_task_started = False @@ -32,75 +39,76 @@ async def background_task(): while True: await sio.sleep(10) count += 1 - await sio.emit('my_response', {'data': 'Server generated event'}) + await sio.emit("my_response", {"data": "Server generated event"}) -@sio.on('my_event') +@sio.on("my_event") async def test_message(sid, message): - await sio.emit('my_response', {'data': message['data']}, room=sid) + await sio.emit("my_response", {"data": message["data"]}, room=sid) -@sio.on('my_broadcast_event') +@sio.on("my_broadcast_event") async def test_broadcast_message(sid, message): - await sio.emit('my_response', {'data': message['data']}) + await sio.emit("my_response", {"data": message["data"]}) -@sio.on('join') +@sio.on("join") async def join(sid, message): - await sio.enter_room(sid, message['room']) - await sio.emit('my_response', {'data': 'Entered room: ' + message['room']}, - room=sid) + await sio.enter_room(sid, message["room"]) + await sio.emit( + "my_response", {"data": "Entered room: " + message["room"]}, room=sid + ) -@sio.on('leave') +@sio.on("leave") async def leave(sid, message): - await sio.leave_room(sid, message['room']) - await sio.emit('my_response', {'data': 'Left room: ' + message['room']}, - room=sid) + await sio.leave_room(sid, message["room"]) + await sio.emit("my_response", {"data": "Left room: " + message["room"]}, room=sid) -@sio.on('close room') +@sio.on("close room") async def close(sid, message): - await sio.emit('my_response', - {'data': 'Room ' + message['room'] + ' is closing.'}, - room=message['room']) - await sio.close_room(message['room']) + await sio.emit( + "my_response", + {"data": "Room " + message["room"] + " is closing."}, + room=message["room"], + ) + await sio.close_room(message["room"]) -@sio.on('my_room_event') +@sio.on("my_room_event") async def send_room_message(sid, message): - await sio.emit('my_response', {'data': message['data']}, - room=message['room']) + await sio.emit("my_response", {"data": message["data"]}, room=message["room"]) -@sio.on('disconnect request') +@sio.on("disconnect request") async def disconnect_request(sid): await sio.disconnect(sid) -@sio.on('connect') +@sio.on("connect") async def test_connect(sid, environ): global background_task_started if not background_task_started: sio.start_background_task(background_task) background_task_started = True - await sio.emit('my_response', {'data': 'Connected', 'count': 0}, room=sid) + await sio.emit("my_response", {"data": "Connected", "count": 0}, room=sid) -@sio.on('disconnect') +@sio.on("disconnect") def test_disconnect(sid, reason): - print('Client disconnected, reason:', reason) + print("Client disconnected, reason:", reason) -if __name__ == '__main__': +if __name__ == "__main__": if instrument: - print('The server is instrumented for remote administration.') + 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:' + "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) + 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/asgi/fastapi-fiddle.py b/examples/server/asgi/fastapi-fiddle.py index b6902e0..db52c1a 100644 --- a/examples/server/asgi/fastapi-fiddle.py +++ b/examples/server/asgi/fastapi-fiddle.py @@ -1,37 +1,38 @@ #!/usr/bin/env python +import uvicorn from fastapi import FastAPI from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles + import socketio -import uvicorn app = FastAPI() -app.mount('/static', StaticFiles(directory='static'), name='static') +app.mount("/static", StaticFiles(directory="static"), name="static") -sio = socketio.AsyncServer(async_mode='asgi') +sio = socketio.AsyncServer(async_mode="asgi") combined_asgi_app = socketio.ASGIApp(sio, app) -@app.get('/') +@app.get("/") async def index(): - return FileResponse('fiddle.html') + return FileResponse("fiddle.html") -@app.get('/hello') +@app.get("/hello") async def hello(): - return {'message': 'Hello, World!'} + return {"message": "Hello, World!"} @sio.event async def connect(sid, environ, auth): - print(f'connected auth={auth} sid={sid}') - await sio.emit('hello', (1, 2, {'hello': 'you'}), to=sid) + print(f"connected auth={auth} sid={sid}") + await sio.emit("hello", (1, 2, {"hello": "you"}), to=sid) @sio.event def disconnect(sid): - print('disconnected', sid) + print("disconnected", sid) -if __name__ == '__main__': - uvicorn.run(combined_asgi_app, host='127.0.0.1', port=5000) +if __name__ == "__main__": + uvicorn.run(combined_asgi_app, host="127.0.0.1", port=5000) diff --git a/examples/server/asgi/fiddle.py b/examples/server/asgi/fiddle.py index 402a379..7b1b01b 100644 --- a/examples/server/asgi/fiddle.py +++ b/examples/server/asgi/fiddle.py @@ -3,23 +3,26 @@ import uvicorn import socketio -sio = socketio.AsyncServer(async_mode='asgi') -app = socketio.ASGIApp(sio, static_files={ - '/': 'fiddle.html', - '/static': 'static', -}) +sio = socketio.AsyncServer(async_mode="asgi") +app = socketio.ASGIApp( + sio, + static_files={ + "/": "fiddle.html", + "/static": "static", + }, +) @sio.event async def connect(sid, environ, auth): - print(f'connected auth={auth} sid={sid}') - await sio.emit('hello', (1, 2, {'hello': 'you'}), to=sid) + print(f"connected auth={auth} sid={sid}") + await sio.emit("hello", (1, 2, {"hello": "you"}), to=sid) @sio.event def disconnect(sid, reason): - print('disconnected', sid, reason) + print("disconnected", sid, reason) -if __name__ == '__main__': - uvicorn.run(app, host='127.0.0.1', port=5000) +if __name__ == "__main__": + uvicorn.run(app, host="127.0.0.1", port=5000) diff --git a/examples/server/asgi/latency.py b/examples/server/asgi/latency.py index 3a9d2de..ee25192 100644 --- a/examples/server/asgi/latency.py +++ b/examples/server/asgi/latency.py @@ -3,17 +3,20 @@ import uvicorn import socketio -sio = socketio.AsyncServer(async_mode='asgi') -app = socketio.ASGIApp(sio, static_files={ - '/': 'latency.html', - '/static': 'static', -}) +sio = socketio.AsyncServer(async_mode="asgi") +app = socketio.ASGIApp( + sio, + static_files={ + "/": "latency.html", + "/static": "static", + }, +) @sio.event async def ping_from_client(sid): - await sio.emit('pong_from_server', room=sid) + await sio.emit("pong_from_server", room=sid) -if __name__ == '__main__': - uvicorn.run(app, host='127.0.0.1', port=5000) +if __name__ == "__main__": + uvicorn.run(app, host="127.0.0.1", port=5000) diff --git a/examples/server/asgi/litestar-fiddle.py b/examples/server/asgi/litestar-fiddle.py index 9cd9a8a..f5562e3 100644 --- a/examples/server/asgi/litestar-fiddle.py +++ b/examples/server/asgi/litestar-fiddle.py @@ -1,38 +1,41 @@ #!/usr/bin/env python -from litestar import Litestar, get, MediaType +import uvicorn +from litestar import Litestar, MediaType, get from litestar.response import File from litestar.static_files.config import StaticFilesConfig + import socketio -import uvicorn -sio = socketio.AsyncServer(async_mode='asgi') +sio = socketio.AsyncServer(async_mode="asgi") -@get('/', media_type=MediaType.HTML) +@get("/", media_type=MediaType.HTML) async def index() -> File: - return File('fiddle.html', content_disposition_type='inline') + return File("fiddle.html", content_disposition_type="inline") -@get('/hello') +@get("/hello") async def hello() -> dict: - return {'message': 'Hello, World!'} + return {"message": "Hello, World!"} @sio.event async def connect(sid, environ, auth): - print(f'connected auth={auth} sid={sid}') - await sio.emit('hello', (1, 2, {'hello': 'you'}), to=sid) + print(f"connected auth={auth} sid={sid}") + await sio.emit("hello", (1, 2, {"hello": "you"}), to=sid) @sio.event def disconnect(sid): - print('disconnected', sid) + print("disconnected", sid) -app = Litestar([index, hello], static_files_config=[ - StaticFilesConfig('static', directories=['static'])]) +app = Litestar( + [index, hello], + static_files_config=[StaticFilesConfig("static", directories=["static"])], +) combined_asgi_app = socketio.ASGIApp(sio, app) -if __name__ == '__main__': - uvicorn.run(combined_asgi_app, host='127.0.0.1', port=5000) +if __name__ == "__main__": + uvicorn.run(combined_asgi_app, host="127.0.0.1", port=5000) diff --git a/examples/server/sanic/app.py b/examples/server/sanic/app.py index 447ddff..be92e3e 100644 --- a/examples/server/sanic/app.py +++ b/examples/server/sanic/app.py @@ -3,7 +3,7 @@ from sanic.response import html import socketio -sio = socketio.AsyncServer(async_mode='sanic') +sio = socketio.AsyncServer(async_mode="sanic") app = Sanic(__name__) sio.attach(app) @@ -14,56 +14,57 @@ async def background_task(): while True: await sio.sleep(10) count += 1 - await sio.emit('my_response', {'data': 'Server generated event'}) + await sio.emit("my_response", {"data": "Server generated event"}) -@app.listener('before_server_start') +@app.listener("before_server_start") def before_server_start(sanic, loop): sio.start_background_task(background_task) -@app.route('/') +@app.route("/") async def index(request): - with open('app.html') as f: + with open("app.html") as f: return html(f.read()) @sio.event async def my_event(sid, message): - await sio.emit('my_response', {'data': message['data']}, room=sid) + await sio.emit("my_response", {"data": message["data"]}, room=sid) @sio.event async def my_broadcast_event(sid, message): - await sio.emit('my_response', {'data': message['data']}) + await sio.emit("my_response", {"data": message["data"]}) @sio.event async def join(sid, message): - await sio.enter_room(sid, message['room']) - await sio.emit('my_response', {'data': 'Entered room: ' + message['room']}, - room=sid) + await sio.enter_room(sid, message["room"]) + await sio.emit( + "my_response", {"data": "Entered room: " + message["room"]}, room=sid + ) @sio.event async def leave(sid, message): - await sio.leave_room(sid, message['room']) - await sio.emit('my_response', {'data': 'Left room: ' + message['room']}, - room=sid) + await sio.leave_room(sid, message["room"]) + await sio.emit("my_response", {"data": "Left room: " + message["room"]}, room=sid) @sio.event async def close_room(sid, message): - await sio.emit('my_response', - {'data': 'Room ' + message['room'] + ' is closing.'}, - room=message['room']) - await sio.close_room(message['room']) + await sio.emit( + "my_response", + {"data": "Room " + message["room"] + " is closing."}, + room=message["room"], + ) + await sio.close_room(message["room"]) @sio.event async def my_room_event(sid, message): - await sio.emit('my_response', {'data': message['data']}, - room=message['room']) + await sio.emit("my_response", {"data": message["data"]}, room=message["room"]) @sio.event @@ -73,16 +74,16 @@ async def disconnect_request(sid): @sio.event async def connect(sid, environ): - await sio.emit('my_response', {'data': 'Connected', 'count': 0}, room=sid) + await sio.emit("my_response", {"data": "Connected", "count": 0}, room=sid) @sio.event def disconnect(sid, reason): - print('Client disconnected, reason:', reason) + print("Client disconnected, reason:", reason) -app.static('/static', './static') +app.static("/static", "./static") -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/examples/server/sanic/fiddle.py b/examples/server/sanic/fiddle.py index 405e6e5..4774967 100644 --- a/examples/server/sanic/fiddle.py +++ b/examples/server/sanic/fiddle.py @@ -3,30 +3,30 @@ from sanic.response import html import socketio -sio = socketio.AsyncServer(async_mode='sanic') +sio = socketio.AsyncServer(async_mode="sanic") app = Sanic(__name__) sio.attach(app) -@app.route('/') +@app.route("/") def index(request): - with open('fiddle.html') as f: + with open("fiddle.html") as f: return html(f.read()) @sio.event async def connect(sid, environ, auth): - print(f'connected auth={auth} sid={sid}') - await sio.emit('hello', (1, 2, {'hello': 'you'}), to=sid) + print(f"connected auth={auth} sid={sid}") + await sio.emit("hello", (1, 2, {"hello": "you"}), to=sid) @sio.event def disconnect(sid, reason): - print('disconnected', sid, reason) + print("disconnected", sid, reason) -app.static('/static', './static') +app.static("/static", "./static") -if __name__ == '__main__': +if __name__ == "__main__": app.run() diff --git a/examples/server/sanic/latency.py b/examples/server/sanic/latency.py index 8f14992..1287761 100644 --- a/examples/server/sanic/latency.py +++ b/examples/server/sanic/latency.py @@ -3,23 +3,24 @@ from sanic.response import html import socketio -sio = socketio.AsyncServer(async_mode='sanic') +sio = socketio.AsyncServer(async_mode="sanic") app = Sanic(__name__) sio.attach(app) -@app.route('/') +@app.route("/") def index(request): - with open('latency.html') as f: + with open("latency.html") as f: return html(f.read()) @sio.event async def ping_from_client(sid): - await sio.emit('pong_from_server', room=sid) + await sio.emit("pong_from_server", room=sid) -app.static('/static', './static') +app.static("/static", "./static") -if __name__ == '__main__': + +if __name__ == "__main__": app.run() diff --git a/examples/server/tornado/app.py b/examples/server/tornado/app.py index 58317d9..546f531 100644 --- a/examples/server/tornado/app.py +++ b/examples/server/tornado/app.py @@ -1,15 +1,15 @@ import os import tornado.ioloop -from tornado.options import define, options, parse_command_line import tornado.web +from tornado.options import define, options, parse_command_line import socketio define("port", default=5000, help="run on the given port", type=int) define("debug", default=False, help="run in debug mode") -sio = socketio.AsyncServer(async_mode='tornado') +sio = socketio.AsyncServer(async_mode="tornado") async def background_task(): @@ -18,7 +18,7 @@ async def background_task(): while True: await sio.sleep(10) count += 1 - await sio.emit('my_response', {'data': 'Server generated event'}) + await sio.emit("my_response", {"data": "Server generated event"}) class MainHandler(tornado.web.RequestHandler): @@ -28,40 +28,41 @@ class MainHandler(tornado.web.RequestHandler): @sio.event async def my_event(sid, message): - await sio.emit('my_response', {'data': message['data']}, room=sid) + await sio.emit("my_response", {"data": message["data"]}, room=sid) @sio.event async def my_broadcast_event(sid, message): - await sio.emit('my_response', {'data': message['data']}) + await sio.emit("my_response", {"data": message["data"]}) @sio.event async def join(sid, message): - await sio.enter_room(sid, message['room']) - await sio.emit('my_response', {'data': 'Entered room: ' + message['room']}, - room=sid) + await sio.enter_room(sid, message["room"]) + await sio.emit( + "my_response", {"data": "Entered room: " + message["room"]}, room=sid + ) @sio.event async def leave(sid, message): - await sio.leave_room(sid, message['room']) - await sio.emit('my_response', {'data': 'Left room: ' + message['room']}, - room=sid) + await sio.leave_room(sid, message["room"]) + await sio.emit("my_response", {"data": "Left room: " + message["room"]}, room=sid) @sio.event async def close_room(sid, message): - await sio.emit('my_response', - {'data': 'Room ' + message['room'] + ' is closing.'}, - room=message['room']) - await sio.close_room(message['room']) + await sio.emit( + "my_response", + {"data": "Room " + message["room"] + " is closing."}, + room=message["room"], + ) + await sio.close_room(message["room"]) @sio.event async def my_room_event(sid, message): - await sio.emit('my_response', {'data': message['data']}, - room=message['room']) + await sio.emit("my_response", {"data": message["data"]}, room=message["room"]) @sio.event @@ -71,12 +72,12 @@ async def disconnect_request(sid): @sio.event async def connect(sid, environ): - await sio.emit('my_response', {'data': 'Connected', 'count': 0}, room=sid) + await sio.emit("my_response", {"data": "Connected", "count": 0}, room=sid) @sio.event def disconnect(sid, reason): - print('Client disconnected, reason:', reason) + print("Client disconnected, reason:", reason) def main(): diff --git a/examples/server/tornado/fiddle.py b/examples/server/tornado/fiddle.py index b3878a2..620dfdb 100644 --- a/examples/server/tornado/fiddle.py +++ b/examples/server/tornado/fiddle.py @@ -1,15 +1,15 @@ import os import tornado.ioloop -from tornado.options import define, options, parse_command_line import tornado.web +from tornado.options import define, options, parse_command_line import socketio define("port", default=5000, help="run on the given port", type=int) define("debug", default=False, help="run in debug mode") -sio = socketio.AsyncServer(async_mode='tornado') +sio = socketio.AsyncServer(async_mode="tornado") class MainHandler(tornado.web.RequestHandler): @@ -19,13 +19,13 @@ class MainHandler(tornado.web.RequestHandler): @sio.event async def connect(sid, environ, auth): - print(f'connected auth={auth} sid={sid}') - await sio.emit('hello', (1, 2, {'hello': 'you'}), to=sid) + print(f"connected auth={auth} sid={sid}") + await sio.emit("hello", (1, 2, {"hello": "you"}), to=sid) @sio.event def disconnect(sid, reason): - print('disconnected', sid, reason) + print("disconnected", sid, reason) def main(): diff --git a/examples/server/tornado/latency.py b/examples/server/tornado/latency.py index 571050b..a108f15 100644 --- a/examples/server/tornado/latency.py +++ b/examples/server/tornado/latency.py @@ -1,15 +1,15 @@ import os import tornado.ioloop -from tornado.options import define, options, parse_command_line import tornado.web +from tornado.options import define, options, parse_command_line import socketio define("port", default=5000, help="run on the given port", type=int) define("debug", default=False, help="run in debug mode") -sio = socketio.AsyncServer(async_mode='tornado') +sio = socketio.AsyncServer(async_mode="tornado") class MainHandler(tornado.web.RequestHandler): @@ -19,7 +19,7 @@ class MainHandler(tornado.web.RequestHandler): @sio.event async def ping_from_client(sid): - await sio.emit('pong_from_server', room=sid) + await sio.emit("pong_from_server", room=sid) def main(): diff --git a/examples/server/wsgi/app.py b/examples/server/wsgi/app.py index 7fc871b..e55abc8 100644 --- a/examples/server/wsgi/app.py +++ b/examples/server/wsgi/app.py @@ -7,25 +7,29 @@ async_mode = None # Admin UI hosted at https://admin.socket.io instrument = True admin_login = { - 'username': 'admin', - 'password': 'python', # change this to a strong secret for production use! + "username": "admin", + "password": "python", # change this to a strong secret for production use! } from flask import Flask, render_template + import socketio 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 - ]) + 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!' +app.config["SECRET_KEY"] = "secret!" thread = None @@ -35,52 +39,52 @@ def background_thread(): while True: sio.sleep(10) count += 1 - sio.emit('my_response', {'data': 'Server generated event'}) + sio.emit("my_response", {"data": "Server generated event"}) -@app.route('/') +@app.route("/") def index(): global thread if thread is None: thread = sio.start_background_task(background_thread) - return render_template('index.html') + return render_template("index.html") @sio.event def my_event(sid, message): - sio.emit('my_response', {'data': message['data']}, room=sid) + sio.emit("my_response", {"data": message["data"]}, room=sid) @sio.event def my_broadcast_event(sid, message): - sio.emit('my_response', {'data': message['data']}) + sio.emit("my_response", {"data": message["data"]}) @sio.event def join(sid, message): - sio.enter_room(sid, message['room']) - sio.emit('my_response', {'data': 'Entered room: ' + message['room']}, - room=sid) + sio.enter_room(sid, message["room"]) + sio.emit("my_response", {"data": "Entered room: " + message["room"]}, room=sid) @sio.event def leave(sid, message): - sio.leave_room(sid, message['room']) - sio.emit('my_response', {'data': 'Left room: ' + message['room']}, - room=sid) + sio.leave_room(sid, message["room"]) + sio.emit("my_response", {"data": "Left room: " + message["room"]}, room=sid) @sio.event def close_room(sid, message): - sio.emit('my_response', - {'data': 'Room ' + message['room'] + ' is closing.'}, - room=message['room']) - sio.close_room(message['room']) + sio.emit( + "my_response", + {"data": "Room " + message["room"] + " is closing."}, + room=message["room"], + ) + sio.close_room(message["room"]) @sio.event def my_room_event(sid, message): - sio.emit('my_response', {'data': message['data']}, room=message['room']) + sio.emit("my_response", {"data": message["data"]}, room=message["room"]) @sio.event @@ -90,49 +94,55 @@ def disconnect_request(sid): @sio.event def connect(sid, environ): - sio.emit('my_response', {'data': 'Connected', 'count': 0}, room=sid) + sio.emit("my_response", {"data": "Connected", "count": 0}, room=sid) @sio.event def disconnect(sid, reason): - print('Client disconnected, reason:', reason) + print("Client disconnected, reason:", reason) -if __name__ == '__main__': +if __name__ == "__main__": if instrument: - print('The server is instrumented for remote administration.') + 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:' + "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': + 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) - elif sio.async_mode == 'eventlet': + elif sio.async_mode == "eventlet": # deploy with eventlet import eventlet import eventlet.wsgi - eventlet.wsgi.server(eventlet.listen(('', 5000)), app) - elif sio.async_mode == 'gevent': + + eventlet.wsgi.server(eventlet.listen(("", 5000)), app) + elif sio.async_mode == "gevent": # deploy with gevent from gevent import pywsgi + try: from geventwebsocket.handler import WebSocketHandler + websocket = True except ImportError: websocket = False if websocket: - pywsgi.WSGIServer(('', 5000), app, - handler_class=WebSocketHandler).serve_forever() + pywsgi.WSGIServer( + ("", 5000), app, handler_class=WebSocketHandler + ).serve_forever() else: - pywsgi.WSGIServer(('', 5000), app).serve_forever() - elif sio.async_mode == 'gevent_uwsgi': - print('Start the application through the uwsgi server. Example:') - print('uwsgi --http :5000 --gevent 1000 --http-websockets --master ' - '--wsgi-file app.py --callable app') + pywsgi.WSGIServer(("", 5000), app).serve_forever() + elif sio.async_mode == "gevent_uwsgi": + print("Start the application through the uwsgi server. Example:") + print( + "uwsgi --http :5000 --gevent 1000 --http-websockets --master " + "--wsgi-file app.py --callable app" + ) else: - print('Unknown async_mode: ' + sio.async_mode) + print("Unknown async_mode: " + sio.async_mode) diff --git a/examples/server/wsgi/django_socketio/django_socketio/asgi.py b/examples/server/wsgi/django_socketio/django_socketio/asgi.py index 234d2d9..2c1e775 100644 --- a/examples/server/wsgi/django_socketio/django_socketio/asgi.py +++ b/examples/server/wsgi/django_socketio/django_socketio/asgi.py @@ -11,6 +11,6 @@ import os from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_socketio.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_socketio.settings") application = get_asgi_application() diff --git a/examples/server/wsgi/django_socketio/django_socketio/settings.py b/examples/server/wsgi/django_socketio/django_socketio/settings.py index 313462d..83cd23d 100644 --- a/examples/server/wsgi/django_socketio/django_socketio/settings.py +++ b/examples/server/wsgi/django_socketio/django_socketio/settings.py @@ -20,7 +20,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent # See https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = 'django-insecure-&@-nkbrpe@%1_%ljh#oe@sw)6+k(&yn#r_)!5p)$22c^u#0@lj' +SECRET_KEY = "django-insecure-&@-nkbrpe@%1_%ljh#oe@sw)6+k(&yn#r_)!5p)$22c^u#0@lj" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -31,53 +31,53 @@ ALLOWED_HOSTS = [] # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', - 'django.contrib.auth', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.messages', - 'django.contrib.staticfiles', - 'socketio_app', + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", + "socketio_app", ] MIDDLEWARE = [ - 'django.middleware.security.SecurityMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.common.CommonMiddleware', - 'django.middleware.csrf.CsrfViewMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', - 'django.contrib.messages.middleware.MessageMiddleware', - 'django.middleware.clickjacking.XFrameOptionsMiddleware', + "django.middleware.security.SecurityMiddleware", + "django.contrib.sessions.middleware.SessionMiddleware", + "django.middleware.common.CommonMiddleware", + "django.middleware.csrf.CsrfViewMiddleware", + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", ] -ROOT_URLCONF = 'django_socketio.urls' +ROOT_URLCONF = "django_socketio.urls" TEMPLATES = [ { - 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [], - 'APP_DIRS': True, - 'OPTIONS': { - 'context_processors': [ - 'django.template.context_processors.debug', - 'django.template.context_processors.request', - 'django.contrib.auth.context_processors.auth', - 'django.contrib.messages.context_processors.messages', + "BACKEND": "django.template.backends.django.DjangoTemplates", + "DIRS": [], + "APP_DIRS": True, + "OPTIONS": { + "context_processors": [ + "django.template.context_processors.debug", + "django.template.context_processors.request", + "django.contrib.auth.context_processors.auth", + "django.contrib.messages.context_processors.messages", ], }, }, ] -WSGI_APPLICATION = 'django_socketio.wsgi.application' +WSGI_APPLICATION = "django_socketio.wsgi.application" # Database # https://docs.djangoproject.com/en/4.0/ref/settings/#databases DATABASES = { - 'default': { - 'ENGINE': 'django.db.backends.sqlite3', - 'NAME': BASE_DIR / 'db.sqlite3', + "default": { + "ENGINE": "django.db.backends.sqlite3", + "NAME": BASE_DIR / "db.sqlite3", } } @@ -87,16 +87,16 @@ DATABASES = { AUTH_PASSWORD_VALIDATORS = [ { - 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', + "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', + "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', + "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { - 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', + "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] @@ -104,9 +104,9 @@ AUTH_PASSWORD_VALIDATORS = [ # Internationalization # https://docs.djangoproject.com/en/4.0/topics/i18n/ -LANGUAGE_CODE = 'en-us' +LANGUAGE_CODE = "en-us" -TIME_ZONE = 'UTC' +TIME_ZONE = "UTC" USE_I18N = True @@ -116,9 +116,9 @@ USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.0/howto/static-files/ -STATIC_URL = 'static/' +STATIC_URL = "static/" # Default primary key field type # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field -DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' +DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField" diff --git a/examples/server/wsgi/django_socketio/django_socketio/urls.py b/examples/server/wsgi/django_socketio/django_socketio/urls.py index c156551..79fd5d5 100644 --- a/examples/server/wsgi/django_socketio/django_socketio/urls.py +++ b/examples/server/wsgi/django_socketio/django_socketio/urls.py @@ -13,11 +13,12 @@ Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ + +from django.conf.urls import include from django.contrib import admin from django.urls import path -from django.conf.urls import include urlpatterns = [ - path('admin/', admin.site.urls), - path(r'', include('socketio_app.urls')), + path("admin/", admin.site.urls), + path(r"", include("socketio_app.urls")), ] diff --git a/examples/server/wsgi/django_socketio/django_socketio/wsgi.py b/examples/server/wsgi/django_socketio/django_socketio/wsgi.py index 771cae2..9e47c76 100644 --- a/examples/server/wsgi/django_socketio/django_socketio/wsgi.py +++ b/examples/server/wsgi/django_socketio/django_socketio/wsgi.py @@ -10,11 +10,11 @@ https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ import os from django.core.wsgi import get_wsgi_application -import socketio - from socketio_app.views import sio -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_socketio.settings') +import socketio + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_socketio.settings") django_app = get_wsgi_application() application = socketio.WSGIApp(sio, django_app) diff --git a/examples/server/wsgi/django_socketio/manage.py b/examples/server/wsgi/django_socketio/manage.py index 35ef79b..2d7d552 100755 --- a/examples/server/wsgi/django_socketio/manage.py +++ b/examples/server/wsgi/django_socketio/manage.py @@ -1,12 +1,13 @@ #!/usr/bin/env python """Django's command-line utility for administrative tasks.""" + import os import sys def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_socketio.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_socketio.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: @@ -18,5 +19,5 @@ def main(): execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/examples/server/wsgi/django_socketio/socketio_app/admin.py b/examples/server/wsgi/django_socketio/socketio_app/admin.py index 8c38f3f..846f6b4 100644 --- a/examples/server/wsgi/django_socketio/socketio_app/admin.py +++ b/examples/server/wsgi/django_socketio/socketio_app/admin.py @@ -1,3 +1 @@ -from django.contrib import admin - # Register your models here. diff --git a/examples/server/wsgi/django_socketio/socketio_app/apps.py b/examples/server/wsgi/django_socketio/socketio_app/apps.py index e8e83ea..4f8c8dc 100644 --- a/examples/server/wsgi/django_socketio/socketio_app/apps.py +++ b/examples/server/wsgi/django_socketio/socketio_app/apps.py @@ -2,5 +2,5 @@ from django.apps import AppConfig class SocketioAppConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'socketio_app' + default_auto_field = "django.db.models.BigAutoField" + name = "socketio_app" diff --git a/examples/server/wsgi/django_socketio/socketio_app/models.py b/examples/server/wsgi/django_socketio/socketio_app/models.py index 71a8362..6b20219 100644 --- a/examples/server/wsgi/django_socketio/socketio_app/models.py +++ b/examples/server/wsgi/django_socketio/socketio_app/models.py @@ -1,3 +1 @@ -from django.db import models - # Create your models here. diff --git a/examples/server/wsgi/django_socketio/socketio_app/tests.py b/examples/server/wsgi/django_socketio/socketio_app/tests.py index 7ce503c..a39b155 100644 --- a/examples/server/wsgi/django_socketio/socketio_app/tests.py +++ b/examples/server/wsgi/django_socketio/socketio_app/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/examples/server/wsgi/django_socketio/socketio_app/urls.py b/examples/server/wsgi/django_socketio/socketio_app/urls.py index eddc173..d98c635 100644 --- a/examples/server/wsgi/django_socketio/socketio_app/urls.py +++ b/examples/server/wsgi/django_socketio/socketio_app/urls.py @@ -3,5 +3,5 @@ from django.urls import path from . import views urlpatterns = [ - path(r'', views.index, name='index'), + path(r"", views.index, name="index"), ] diff --git a/examples/server/wsgi/django_socketio/socketio_app/views.py b/examples/server/wsgi/django_socketio/socketio_app/views.py index f54e1d6..cb470a6 100644 --- a/examples/server/wsgi/django_socketio/socketio_app/views.py +++ b/examples/server/wsgi/django_socketio/socketio_app/views.py @@ -6,6 +6,7 @@ async_mode = None import os from django.http import HttpResponse + import socketio basedir = os.path.dirname(os.path.realpath(__file__)) @@ -17,7 +18,7 @@ def index(request): global thread if thread is None: thread = sio.start_background_task(background_thread) - return HttpResponse(open(os.path.join(basedir, 'static/index.html'))) + return HttpResponse(open(os.path.join(basedir, "static/index.html"))) def background_thread(): @@ -26,45 +27,44 @@ def background_thread(): while True: sio.sleep(10) count += 1 - sio.emit('my_response', {'data': 'Server generated event'}, - namespace='/test') + sio.emit("my_response", {"data": "Server generated event"}, namespace="/test") @sio.event def my_event(sid, message): - sio.emit('my_response', {'data': message['data']}, room=sid) + sio.emit("my_response", {"data": message["data"]}, room=sid) @sio.event def my_broadcast_event(sid, message): - sio.emit('my_response', {'data': message['data']}) + sio.emit("my_response", {"data": message["data"]}) @sio.event def join(sid, message): - sio.enter_room(sid, message['room']) - sio.emit('my_response', {'data': 'Entered room: ' + message['room']}, - room=sid) + sio.enter_room(sid, message["room"]) + sio.emit("my_response", {"data": "Entered room: " + message["room"]}, room=sid) @sio.event def leave(sid, message): - sio.leave_room(sid, message['room']) - sio.emit('my_response', {'data': 'Left room: ' + message['room']}, - room=sid) + sio.leave_room(sid, message["room"]) + sio.emit("my_response", {"data": "Left room: " + message["room"]}, room=sid) @sio.event def close_room(sid, message): - sio.emit('my_response', - {'data': 'Room ' + message['room'] + ' is closing.'}, - room=message['room']) - sio.close_room(message['room']) + sio.emit( + "my_response", + {"data": "Room " + message["room"] + " is closing."}, + room=message["room"], + ) + sio.close_room(message["room"]) @sio.event def my_room_event(sid, message): - sio.emit('my_response', {'data': message['data']}, room=message['room']) + sio.emit("my_response", {"data": message["data"]}, room=message["room"]) @sio.event @@ -74,9 +74,9 @@ def disconnect_request(sid): @sio.event def connect(sid, environ): - sio.emit('my_response', {'data': 'Connected', 'count': 0}, room=sid) + sio.emit("my_response", {"data": "Connected", "count": 0}, room=sid) @sio.event def disconnect(sid, reason): - print('Client disconnected, reason:', reason) + print("Client disconnected, reason:", reason) diff --git a/examples/server/wsgi/fiddle.py b/examples/server/wsgi/fiddle.py index e9cd703..f532e1e 100644 --- a/examples/server/wsgi/fiddle.py +++ b/examples/server/wsgi/fiddle.py @@ -4,6 +4,7 @@ async_mode = None from flask import Flask, render_template + import socketio sio = socketio.Server(async_mode=async_mode) @@ -11,47 +12,53 @@ app = Flask(__name__) app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app) -@app.route('/') +@app.route("/") def index(): - return render_template('fiddle.html') + return render_template("fiddle.html") @sio.event def connect(sid, environ, auth): - print(f'connected auth={auth} sid={sid}') - sio.emit('hello', (1, 2, {'hello': 'you'}), to=sid) + print(f"connected auth={auth} sid={sid}") + sio.emit("hello", (1, 2, {"hello": "you"}), to=sid) @sio.event def disconnect(sid, reason): - print('disconnected', sid, reason) + print("disconnected", sid, reason) -if __name__ == '__main__': - if sio.async_mode == 'threading': +if __name__ == "__main__": + if sio.async_mode == "threading": # deploy with Werkzeug app.run(threaded=True) - elif sio.async_mode == 'eventlet': + elif sio.async_mode == "eventlet": # deploy with eventlet import eventlet import eventlet.wsgi - eventlet.wsgi.server(eventlet.listen(('', 5000)), app) - elif sio.async_mode == 'gevent': + + eventlet.wsgi.server(eventlet.listen(("", 5000)), app) + elif sio.async_mode == "gevent": # deploy with gevent from gevent import pywsgi + try: from geventwebsocket.handler import WebSocketHandler + websocket = True except ImportError: websocket = False if websocket: - pywsgi.WSGIServer(('', 5000), app, - handler_class=WebSocketHandler).serve_forever() + pywsgi.WSGIServer( + ("", 5000), app, handler_class=WebSocketHandler + ).serve_forever() else: - pywsgi.WSGIServer(('', 5000), app).serve_forever() - elif sio.async_mode == 'gevent_uwsgi': - print('Start the application through the uwsgi server. Example:') - print('uwsgi --http :5000 --gevent 1000 --http-websockets --master ' - '--wsgi-file latency.py --callable app') + pywsgi.WSGIServer(("", 5000), app).serve_forever() + elif sio.async_mode == "gevent_uwsgi": + print("Start the application through the uwsgi server. Example:") + print( + "uwsgi --http :5000 --gevent 1000 --http-websockets --master " + "--wsgi-file latency.py --callable app" + ) else: - print('Unknown async_mode: ' + sio.async_mode) + print("Unknown async_mode: " + sio.async_mode) diff --git a/examples/server/wsgi/latency.py b/examples/server/wsgi/latency.py index 5f9ff88..afefe19 100644 --- a/examples/server/wsgi/latency.py +++ b/examples/server/wsgi/latency.py @@ -4,6 +4,7 @@ async_mode = None from flask import Flask, render_template + import socketio sio = socketio.Server(async_mode=async_mode) @@ -11,41 +12,47 @@ app = Flask(__name__) app.wsgi_app = socketio.WSGIApp(sio, app.wsgi_app) -@app.route('/') +@app.route("/") def index(): - return render_template('latency.html') + return render_template("latency.html") @sio.event def ping_from_client(sid): - sio.emit('pong_from_server', room=sid) + sio.emit("pong_from_server", room=sid) -if __name__ == '__main__': - if sio.async_mode == 'threading': +if __name__ == "__main__": + if sio.async_mode == "threading": # deploy with Werkzeug app.run(threaded=True) - elif sio.async_mode == 'eventlet': + elif sio.async_mode == "eventlet": # deploy with eventlet import eventlet import eventlet.wsgi - eventlet.wsgi.server(eventlet.listen(('', 5000)), app) - elif sio.async_mode == 'gevent': + + eventlet.wsgi.server(eventlet.listen(("", 5000)), app) + elif sio.async_mode == "gevent": # deploy with gevent from gevent import pywsgi + try: from geventwebsocket.handler import WebSocketHandler + websocket = True except ImportError: websocket = False if websocket: - pywsgi.WSGIServer(('', 5000), app, - handler_class=WebSocketHandler).serve_forever() + pywsgi.WSGIServer( + ("", 5000), app, handler_class=WebSocketHandler + ).serve_forever() else: - pywsgi.WSGIServer(('', 5000), app).serve_forever() - elif sio.async_mode == 'gevent_uwsgi': - print('Start the application through the uwsgi server. Example:') - print('uwsgi --http :5000 --gevent 1000 --http-websockets --master ' - '--wsgi-file latency.py --callable app') + pywsgi.WSGIServer(("", 5000), app).serve_forever() + elif sio.async_mode == "gevent_uwsgi": + print("Start the application through the uwsgi server. Example:") + print( + "uwsgi --http :5000 --gevent 1000 --http-websockets --master " + "--wsgi-file latency.py --callable app" + ) else: - print('Unknown async_mode: ' + sio.async_mode) + print("Unknown async_mode: " + sio.async_mode) diff --git a/examples/simple-client/async/fiddle_client.py b/examples/simple-client/async/fiddle_client.py index d974480..391cded 100644 --- a/examples/simple-client/async/fiddle_client.py +++ b/examples/simple-client/async/fiddle_client.py @@ -1,12 +1,13 @@ import asyncio + import socketio async def main(): async with socketio.AsyncSimpleClient() as sio: - await sio.connect('http://localhost:5000', auth={'token': 'my-token'}) + await sio.connect("http://localhost:5000", auth={"token": "my-token"}) print(await sio.receive()) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/examples/simple-client/async/latency_client.py b/examples/simple-client/async/latency_client.py index 8d69a85..fcec1f3 100644 --- a/examples/simple-client/async/latency_client.py +++ b/examples/simple-client/async/latency_client.py @@ -1,21 +1,22 @@ import asyncio import time + import socketio async def main(): async with socketio.AsyncSimpleClient() as sio: - await sio.connect('http://localhost:5000') + await sio.connect("http://localhost:5000") while True: start_timer = time.time() - await sio.emit('ping_from_client') - while (await sio.receive()) != ['pong_from_server']: + await sio.emit("ping_from_client") + while (await sio.receive()) != ["pong_from_server"]: pass latency = time.time() - start_timer - print(f'latency is {latency * 1000:.2f} ms') + print(f"latency is {latency * 1000:.2f} ms") await asyncio.sleep(1) -if __name__ == '__main__': +if __name__ == "__main__": asyncio.run(main()) diff --git a/examples/simple-client/sync/fiddle_client.py b/examples/simple-client/sync/fiddle_client.py index 1be759c..7a48144 100644 --- a/examples/simple-client/sync/fiddle_client.py +++ b/examples/simple-client/sync/fiddle_client.py @@ -3,9 +3,9 @@ import socketio def main(): with socketio.SimpleClient() as sio: - sio.connect('http://localhost:5000', auth={'token': 'my-token'}) + sio.connect("http://localhost:5000", auth={"token": "my-token"}) print(sio.receive()) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/examples/simple-client/sync/latency_client.py b/examples/simple-client/sync/latency_client.py index c4dea11..040d081 100644 --- a/examples/simple-client/sync/latency_client.py +++ b/examples/simple-client/sync/latency_client.py @@ -1,20 +1,21 @@ import time + import socketio def main(): with socketio.SimpleClient() as sio: - sio.connect('http://localhost:5000') + sio.connect("http://localhost:5000") while True: start_timer = time.time() - sio.emit('ping_from_client') - while sio.receive() != ['pong_from_server']: + sio.emit("ping_from_client") + while sio.receive() != ["pong_from_server"]: pass latency = time.time() - start_timer - print(f'latency is {latency * 1000:.2f} ms') + print(f"latency is {latency * 1000:.2f} ms") time.sleep(1) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..cbceedf --- /dev/null +++ b/poetry.lock @@ -0,0 +1,1213 @@ +# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. + +[[package]] +name = "aiohappyeyeballs" +version = "2.4.4" +description = "Happy Eyeballs for asyncio" +optional = true +python-versions = ">=3.8" +files = [ + {file = "aiohappyeyeballs-2.4.4-py3-none-any.whl", hash = "sha256:a980909d50efcd44795c4afeca523296716d50cd756ddca6af8c65b996e27de8"}, + {file = "aiohappyeyeballs-2.4.4.tar.gz", hash = "sha256:5fdd7d87889c63183afc18ce9271f9b0a7d32c2303e394468dd45d514a757745"}, +] + +[[package]] +name = "aiohttp" +version = "3.10.11" +description = "Async http client/server framework (asyncio)" +optional = true +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5077b1a5f40ffa3ba1f40d537d3bec4383988ee51fbba6b74aa8fb1bc466599e"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8d6a14a4d93b5b3c2891fca94fa9d41b2322a68194422bef0dd5ec1e57d7d298"}, + {file = "aiohttp-3.10.11-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ffbfde2443696345e23a3c597049b1dd43049bb65337837574205e7368472177"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:20b3d9e416774d41813bc02fdc0663379c01817b0874b932b81c7f777f67b217"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2b943011b45ee6bf74b22245c6faab736363678e910504dd7531a58c76c9015a"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48bc1d924490f0d0b3658fe5c4b081a4d56ebb58af80a6729d4bd13ea569797a"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e12eb3f4b1f72aaaf6acd27d045753b18101524f72ae071ae1c91c1cd44ef115"}, + {file = "aiohttp-3.10.11-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f14ebc419a568c2eff3c1ed35f634435c24ead2fe19c07426af41e7adb68713a"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:72b191cdf35a518bfc7ca87d770d30941decc5aaf897ec8b484eb5cc8c7706f3"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:5ab2328a61fdc86424ee540d0aeb8b73bbcad7351fb7cf7a6546fc0bcffa0038"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:aa93063d4af05c49276cf14e419550a3f45258b6b9d1f16403e777f1addf4519"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:30283f9d0ce420363c24c5c2421e71a738a2155f10adbb1a11a4d4d6d2715cfc"}, + {file = "aiohttp-3.10.11-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:e5358addc8044ee49143c546d2182c15b4ac3a60be01c3209374ace05af5733d"}, + {file = "aiohttp-3.10.11-cp310-cp310-win32.whl", hash = "sha256:e1ffa713d3ea7cdcd4aea9cddccab41edf6882fa9552940344c44e59652e1120"}, + {file = "aiohttp-3.10.11-cp310-cp310-win_amd64.whl", hash = "sha256:778cbd01f18ff78b5dd23c77eb82987ee4ba23408cbed233009fd570dda7e674"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:80ff08556c7f59a7972b1e8919f62e9c069c33566a6d28586771711e0eea4f07"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c8f96e9ee19f04c4914e4e7a42a60861066d3e1abf05c726f38d9d0a466e695"}, + {file = "aiohttp-3.10.11-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fb8601394d537da9221947b5d6e62b064c9a43e88a1ecd7414d21a1a6fba9c24"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ea224cf7bc2d8856d6971cea73b1d50c9c51d36971faf1abc169a0d5f85a382"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:db9503f79e12d5d80b3efd4d01312853565c05367493379df76d2674af881caa"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0f449a50cc33f0384f633894d8d3cd020e3ccef81879c6e6245c3c375c448625"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:82052be3e6d9e0c123499127782a01a2b224b8af8c62ab46b3f6197035ad94e9"}, + {file = "aiohttp-3.10.11-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:20063c7acf1eec550c8eb098deb5ed9e1bb0521613b03bb93644b810986027ac"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:489cced07a4c11488f47aab1f00d0c572506883f877af100a38f1fedaa884c3a"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:ea9b3bab329aeaa603ed3bf605f1e2a6f36496ad7e0e1aa42025f368ee2dc07b"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:ca117819d8ad113413016cb29774b3f6d99ad23c220069789fc050267b786c16"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2dfb612dcbe70fb7cdcf3499e8d483079b89749c857a8f6e80263b021745c730"}, + {file = "aiohttp-3.10.11-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:f9b615d3da0d60e7d53c62e22b4fd1c70f4ae5993a44687b011ea3a2e49051b8"}, + {file = "aiohttp-3.10.11-cp311-cp311-win32.whl", hash = "sha256:29103f9099b6068bbdf44d6a3d090e0a0b2be6d3c9f16a070dd9d0d910ec08f9"}, + {file = "aiohttp-3.10.11-cp311-cp311-win_amd64.whl", hash = "sha256:236b28ceb79532da85d59aa9b9bf873b364e27a0acb2ceaba475dc61cffb6f3f"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:7480519f70e32bfb101d71fb9a1f330fbd291655a4c1c922232a48c458c52710"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f65267266c9aeb2287a6622ee2bb39490292552f9fbf851baabc04c9f84e048d"}, + {file = "aiohttp-3.10.11-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7400a93d629a0608dc1d6c55f1e3d6e07f7375745aaa8bd7f085571e4d1cee97"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f34b97e4b11b8d4eb2c3a4f975be626cc8af99ff479da7de49ac2c6d02d35725"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e7b825da878464a252ccff2958838f9caa82f32a8dbc334eb9b34a026e2c636"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f9f92a344c50b9667827da308473005f34767b6a2a60d9acff56ae94f895f385"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f1ab987a27b83c5268a17218463c2ec08dbb754195113867a27b166cd6087"}, + {file = "aiohttp-3.10.11-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1dc0f4ca54842173d03322793ebcf2c8cc2d34ae91cc762478e295d8e361e03f"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7ce6a51469bfaacff146e59e7fb61c9c23006495d11cc24c514a455032bcfa03"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:aad3cd91d484d065ede16f3cf15408254e2469e3f613b241a1db552c5eb7ab7d"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f4df4b8ca97f658c880fb4b90b1d1ec528315d4030af1ec763247ebfd33d8b9a"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:2e4e18a0a2d03531edbc06c366954e40a3f8d2a88d2b936bbe78a0c75a3aab3e"}, + {file = "aiohttp-3.10.11-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:6ce66780fa1a20e45bc753cda2a149daa6dbf1561fc1289fa0c308391c7bc0a4"}, + {file = "aiohttp-3.10.11-cp312-cp312-win32.whl", hash = "sha256:a919c8957695ea4c0e7a3e8d16494e3477b86f33067478f43106921c2fef15bb"}, + {file = "aiohttp-3.10.11-cp312-cp312-win_amd64.whl", hash = "sha256:b5e29706e6389a2283a91611c91bf24f218962717c8f3b4e528ef529d112ee27"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:703938e22434d7d14ec22f9f310559331f455018389222eed132808cd8f44127"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:9bc50b63648840854e00084c2b43035a62e033cb9b06d8c22b409d56eb098413"}, + {file = "aiohttp-3.10.11-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:5f0463bf8b0754bc744e1feb61590706823795041e63edf30118a6f0bf577461"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f6c6dec398ac5a87cb3a407b068e1106b20ef001c344e34154616183fe684288"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bcaf2d79104d53d4dcf934f7ce76d3d155302d07dae24dff6c9fffd217568067"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:25fd5470922091b5a9aeeb7e75be609e16b4fba81cdeaf12981393fb240dd10e"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bbde2ca67230923a42161b1f408c3992ae6e0be782dca0c44cb3206bf330dee1"}, + {file = "aiohttp-3.10.11-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:249c8ff8d26a8b41a0f12f9df804e7c685ca35a207e2410adbd3e924217b9006"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:878ca6a931ee8c486a8f7b432b65431d095c522cbeb34892bee5be97b3481d0f"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:8663f7777ce775f0413324be0d96d9730959b2ca73d9b7e2c2c90539139cbdd6"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:6cd3f10b01f0c31481fba8d302b61603a2acb37b9d30e1d14e0f5a58b7b18a31"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:4e8d8aad9402d3aa02fdc5ca2fe68bcb9fdfe1f77b40b10410a94c7f408b664d"}, + {file = "aiohttp-3.10.11-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:38e3c4f80196b4f6c3a85d134a534a56f52da9cb8d8e7af1b79a32eefee73a00"}, + {file = "aiohttp-3.10.11-cp313-cp313-win32.whl", hash = "sha256:fc31820cfc3b2863c6e95e14fcf815dc7afe52480b4dc03393c4873bb5599f71"}, + {file = "aiohttp-3.10.11-cp313-cp313-win_amd64.whl", hash = "sha256:4996ff1345704ffdd6d75fb06ed175938c133425af616142e7187f28dc75f14e"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74baf1a7d948b3d640badeac333af581a367ab916b37e44cf90a0334157cdfd2"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:473aebc3b871646e1940c05268d451f2543a1d209f47035b594b9d4e91ce8339"}, + {file = "aiohttp-3.10.11-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c2f746a6968c54ab2186574e15c3f14f3e7f67aef12b761e043b33b89c5b5f95"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d110cabad8360ffa0dec8f6ec60e43286e9d251e77db4763a87dcfe55b4adb92"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e0099c7d5d7afff4202a0c670e5b723f7718810000b4abcbc96b064129e64bc7"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0316e624b754dbbf8c872b62fe6dcb395ef20c70e59890dfa0de9eafccd2849d"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a5f7ab8baf13314e6b2485965cbacb94afff1e93466ac4d06a47a81c50f9cca"}, + {file = "aiohttp-3.10.11-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c891011e76041e6508cbfc469dd1a8ea09bc24e87e4c204e05f150c4c455a5fa"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:9208299251370ee815473270c52cd3f7069ee9ed348d941d574d1457d2c73e8b"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:459f0f32c8356e8125f45eeff0ecf2b1cb6db1551304972702f34cd9e6c44658"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:14cdc8c1810bbd4b4b9f142eeee23cda528ae4e57ea0923551a9af4820980e39"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:971aa438a29701d4b34e4943e91b5e984c3ae6ccbf80dd9efaffb01bd0b243a9"}, + {file = "aiohttp-3.10.11-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:9a309c5de392dfe0f32ee57fa43ed8fc6ddf9985425e84bd51ed66bb16bce3a7"}, + {file = "aiohttp-3.10.11-cp38-cp38-win32.whl", hash = "sha256:9ec1628180241d906a0840b38f162a3215114b14541f1a8711c368a8739a9be4"}, + {file = "aiohttp-3.10.11-cp38-cp38-win_amd64.whl", hash = "sha256:9c6e0ffd52c929f985c7258f83185d17c76d4275ad22e90aa29f38e211aacbec"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:cdc493a2e5d8dc79b2df5bec9558425bcd39aff59fc949810cbd0832e294b106"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b3e70f24e7d0405be2348da9d5a7836936bf3a9b4fd210f8c37e8d48bc32eca6"}, + {file = "aiohttp-3.10.11-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:968b8fb2a5eee2770eda9c7b5581587ef9b96fbdf8dcabc6b446d35ccc69df01"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:deef4362af9493d1382ef86732ee2e4cbc0d7c005947bd54ad1a9a16dd59298e"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:686b03196976e327412a1b094f4120778c7c4b9cff9bce8d2fdfeca386b89829"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3bf6d027d9d1d34e1c2e1645f18a6498c98d634f8e373395221121f1c258ace8"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:099fd126bf960f96d34a760e747a629c27fb3634da5d05c7ef4d35ef4ea519fc"}, + {file = "aiohttp-3.10.11-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c73c4d3dae0b4644bc21e3de546530531d6cdc88659cdeb6579cd627d3c206aa"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:0c5580f3c51eea91559db3facd45d72e7ec970b04528b4709b1f9c2555bd6d0b"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:fdf6429f0caabfd8a30c4e2eaecb547b3c340e4730ebfe25139779b9815ba138"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:d97187de3c276263db3564bb9d9fad9e15b51ea10a371ffa5947a5ba93ad6777"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:0acafb350cfb2eba70eb5d271f55e08bd4502ec35e964e18ad3e7d34d71f7261"}, + {file = "aiohttp-3.10.11-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:c13ed0c779911c7998a58e7848954bd4d63df3e3575f591e321b19a2aec8df9f"}, + {file = "aiohttp-3.10.11-cp39-cp39-win32.whl", hash = "sha256:22b7c540c55909140f63ab4f54ec2c20d2635c0289cdd8006da46f3327f971b9"}, + {file = "aiohttp-3.10.11-cp39-cp39-win_amd64.whl", hash = "sha256:7b26b1551e481012575dab8e3727b16fe7dd27eb2711d2e63ced7368756268fb"}, + {file = "aiohttp-3.10.11.tar.gz", hash = "sha256:9dc2b8f3dcab2e39e0fa309c8da50c3b55e6f34ab25f1a71d3288f24924d33a7"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<6.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.12.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = true +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + +[[package]] +name = "alabaster" +version = "0.7.13" +description = "A configurable sidebar-enabled Sphinx theme" +optional = true +python-versions = ">=3.6" +files = [ + {file = "alabaster-0.7.13-py3-none-any.whl", hash = "sha256:1ee19aca801bbabb5ba3f5f258e4422dfa86f82f3e9cefb0859b283cdd7f62a3"}, + {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, +] + +[[package]] +name = "async-timeout" +version = "5.0.1" +description = "Timeout context manager for asyncio programs" +optional = true +python-versions = ">=3.8" +files = [ + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, +] + +[[package]] +name = "attrs" +version = "25.3.0" +description = "Classes Without Boilerplate" +optional = true +python-versions = ">=3.8" +files = [ + {file = "attrs-25.3.0-py3-none-any.whl", hash = "sha256:427318ce031701fea540783410126f03899a97ffc6f61596ad581ac2e40e3bc3"}, + {file = "attrs-25.3.0.tar.gz", hash = "sha256:75d7cefc7fb576747b2c81b4442d4d4a1ce0900973527c011d1030fd3bf4af1b"}, +] + +[package.extras] +benchmark = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-codspeed", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +cov = ["cloudpickle", "coverage[toml] (>=5.3)", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +dev = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pre-commit-uv", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +docs = ["cogapp", "furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier"] +tests = ["cloudpickle", "hypothesis", "mypy (>=1.11.1)", "pympler", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-xdist[psutil]"] +tests-mypy = ["mypy (>=1.11.1)", "pytest-mypy-plugins"] + +[[package]] +name = "babel" +version = "2.17.0" +description = "Internationalization utilities" +optional = true +python-versions = ">=3.8" +files = [ + {file = "babel-2.17.0-py3-none-any.whl", hash = "sha256:4d0b53093fdfb4b21c92b5213dba5a1b23885afa8383709427046b21c366e5f2"}, + {file = "babel-2.17.0.tar.gz", hash = "sha256:0c54cffb19f690cdcc52a3b50bcbf71e07a808d1c80d549f2459b9d2cf0afb9d"}, +] + +[package.dependencies] +pytz = {version = ">=2015.7", markers = "python_version < \"3.9\""} + +[package.extras] +dev = ["backports.zoneinfo", "freezegun (>=1.0,<2.0)", "jinja2 (>=3.0)", "pytest (>=6.0)", "pytest-cov", "pytz", "setuptools", "tzdata"] + +[[package]] +name = "bidict" +version = "0.23.1" +description = "The bidirectional mapping library for Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"}, + {file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"}, +] + +[[package]] +name = "certifi" +version = "2025.8.3" +description = "Python package for providing Mozilla's CA Bundle." +optional = true +python-versions = ">=3.7" +files = [ + {file = "certifi-2025.8.3-py3-none-any.whl", hash = "sha256:f6c12493cfb1b06ba2ff328595af9350c65d6644968e5d3a2ffd78699af217a5"}, + {file = "certifi-2025.8.3.tar.gz", hash = "sha256:e564105f78ded564e3ae7c923924435e1daa7463faeab5bb932bc53ffae63407"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.4.3" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = true +python-versions = ">=3.7" +files = [ + {file = "charset_normalizer-3.4.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:fb7f67a1bfa6e40b438170ebdc8158b78dc465a5a67b6dde178a46987b244a72"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:cc9370a2da1ac13f0153780040f465839e6cccb4a1e44810124b4e22483c93fe"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:07a0eae9e2787b586e129fdcbe1af6997f8d0e5abaa0bc98c0e20e124d67e601"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:74d77e25adda8581ffc1c720f1c81ca082921329452eba58b16233ab1842141c"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d0e909868420b7049dafd3a31d45125b31143eec59235311fc4c57ea26a4acd2"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:c6f162aabe9a91a309510d74eeb6507fab5fff92337a15acbe77753d88d9dcf0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:4ca4c094de7771a98d7fbd67d9e5dbf1eb73efa4f744a730437d8a3a5cf994f0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:02425242e96bcf29a49711b0ca9f37e451da7c70562bc10e8ed992a5a7a25cc0"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:78deba4d8f9590fe4dae384aeff04082510a709957e968753ff3c48399f6f92a"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win32.whl", hash = "sha256:d79c198e27580c8e958906f803e63cddb77653731be08851c7df0b1a14a8fc0f"}, + {file = "charset_normalizer-3.4.3-cp310-cp310-win_amd64.whl", hash = "sha256:c6e490913a46fa054e03699c70019ab869e990270597018cef1d8562132c2669"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:b256ee2e749283ef3ddcff51a675ff43798d92d746d1a6e4631bf8c707d22d0b"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:13faeacfe61784e2559e690fc53fa4c5ae97c6fcedb8eb6fb8d0a15b475d2c64"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:00237675befef519d9af72169d8604a067d92755e84fe76492fef5441db05b91"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:585f3b2a80fbd26b048a0be90c5aae8f06605d3c92615911c3a2b03a8a3b796f"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:0e78314bdc32fa80696f72fa16dc61168fda4d6a0c014e0380f9d02f0e5d8a07"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:96b2b3d1a83ad55310de8c7b4a2d04d9277d5591f40761274856635acc5fcb30"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:939578d9d8fd4299220161fdd76e86c6a251987476f5243e8864a7844476ba14"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:fd10de089bcdcd1be95a2f73dbe6254798ec1bda9f450d5828c96f93e2536b9c"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:1e8ac75d72fa3775e0b7cb7e4629cec13b7514d928d15ef8ea06bca03ef01cae"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win32.whl", hash = "sha256:6cf8fd4c04756b6b60146d98cd8a77d0cdae0e1ca20329da2ac85eed779b6849"}, + {file = "charset_normalizer-3.4.3-cp311-cp311-win_amd64.whl", hash = "sha256:31a9a6f775f9bcd865d88ee350f0ffb0e25936a7f930ca98995c05abf1faf21c"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:e28e334d3ff134e88989d90ba04b47d84382a828c061d0d1027b1b12a62b39b1"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:0cacf8f7297b0c4fcb74227692ca46b4a5852f8f4f24b3c766dd94a1075c4884"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:c6fd51128a41297f5409deab284fecbe5305ebd7e5a1f959bee1c054622b7018"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3cfb2aad70f2c6debfbcb717f23b7eb55febc0bb23dcffc0f076009da10c6392"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:1606f4a55c0fd363d754049cdf400175ee96c992b1f8018b993941f221221c5f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:027b776c26d38b7f15b26a5da1044f376455fb3766df8fc38563b4efbc515154"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:42e5088973e56e31e4fa58eb6bd709e42fc03799c11c42929592889a2e54c491"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:cc34f233c9e71701040d772aa7490318673aa7164a0efe3172b2981218c26d93"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:320e8e66157cc4e247d9ddca8e21f427efc7a04bbd0ac8a9faf56583fa543f9f"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win32.whl", hash = "sha256:fb6fecfd65564f208cbf0fba07f107fb661bcd1a7c389edbced3f7a493f70e37"}, + {file = "charset_normalizer-3.4.3-cp312-cp312-win_amd64.whl", hash = "sha256:86df271bf921c2ee3818f0522e9a5b8092ca2ad8b065ece5d7d9d0e9f4849bcc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:14c2a87c65b351109f6abfc424cab3927b3bdece6f706e4d12faaf3d52ee5efe"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:41d1fc408ff5fdfb910200ec0e74abc40387bccb3252f3f27c0676731df2b2c8"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:1bb60174149316da1c35fa5233681f7c0f9f514509b8e399ab70fea5f17e45c9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:30d006f98569de3459c2fc1f2acde170b7b2bd265dc1943e87e1a4efe1b67c31"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:416175faf02e4b0810f1f38bcb54682878a4af94059a1cd63b8747244420801f"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:6aab0f181c486f973bc7262a97f5aca3ee7e1437011ef0c2ec04b5a11d16c927"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:fdabf8315679312cfa71302f9bd509ded4f2f263fb5b765cf1433b39106c3cc9"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:bd28b817ea8c70215401f657edef3a8aa83c29d447fb0b622c35403780ba11d5"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:18343b2d246dc6761a249ba1fb13f9ee9a2bcd95decc767319506056ea4ad4dc"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win32.whl", hash = "sha256:6fb70de56f1859a3f71261cbe41005f56a7842cc348d3aeb26237560bfa5e0ce"}, + {file = "charset_normalizer-3.4.3-cp313-cp313-win_amd64.whl", hash = "sha256:cf1ebb7d78e1ad8ec2a8c4732c7be2e736f6e5123a4146c5b89c9d1f585f8cef"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-macosx_10_13_universal2.whl", hash = "sha256:3cd35b7e8aedeb9e34c41385fda4f73ba609e561faedfae0a9e75e44ac558a15"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b89bc04de1d83006373429975f8ef9e7932534b8cc9ca582e4db7d20d91816db"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:2001a39612b241dae17b4687898843f254f8748b796a2e16f1051a17078d991d"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:8dcfc373f888e4fb39a7bc57e93e3b845e7f462dacc008d9749568b1c4ece096"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:18b97b8404387b96cdbd30ad660f6407799126d26a39ca65729162fd810a99aa"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:ccf600859c183d70eb47e05a44cd80a4ce77394d1ac0f79dbd2dd90a69a3a049"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_ppc64le.whl", hash = "sha256:53cd68b185d98dde4ad8990e56a58dea83a4162161b1ea9272e5c9182ce415e0"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_s390x.whl", hash = "sha256:30a96e1e1f865f78b030d65241c1ee850cdf422d869e9028e2fc1d5e4db73b92"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:d716a916938e03231e86e43782ca7878fb602a125a91e7acb8b5112e2e96ac16"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win32.whl", hash = "sha256:c6dbd0ccdda3a2ba7c2ecd9d77b37f3b5831687d8dc1b6ca5f56a4880cc7b7ce"}, + {file = "charset_normalizer-3.4.3-cp314-cp314-win_amd64.whl", hash = "sha256:73dc19b562516fc9bcf6e5d6e596df0b4eb98d87e4f79f3ae71840e6ed21361c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0f2be7e0cf7754b9a30eb01f4295cc3d4358a479843b31f328afd210e2c7598c"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c60e092517a73c632ec38e290eba714e9627abe9d301c8c8a12ec32c314a2a4b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:252098c8c7a873e17dd696ed98bbe91dbacd571da4b87df3736768efa7a792e4"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:3653fad4fe3ed447a596ae8638b437f827234f01a8cd801842e43f3d0a6b281b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:8999f965f922ae054125286faf9f11bc6932184b93011d138925a1773830bbe9"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:d95bfb53c211b57198bb91c46dd5a2d8018b3af446583aab40074bf7988401cb"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:5b413b0b1bfd94dbf4023ad6945889f374cd24e3f62de58d6bb102c4d9ae534a"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:b5e3b2d152e74e100a9e9573837aba24aab611d39428ded46f4e4022ea7d1942"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:a2d08ac246bb48479170408d6c19f6385fa743e7157d716e144cad849b2dd94b"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win32.whl", hash = "sha256:ec557499516fc90fd374bf2e32349a2887a876fbf162c160e3c01b6849eaf557"}, + {file = "charset_normalizer-3.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:5d8d01eac18c423815ed4f4a2ec3b439d654e55ee4ad610e153cf02faf67ea40"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:70bfc5f2c318afece2f5838ea5e4c3febada0be750fcf4775641052bbba14d05"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:23b6b24d74478dc833444cbd927c338349d6ae852ba53a0d02a2de1fce45b96e"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_ppc64le.manylinux_2_17_ppc64le.manylinux_2_28_ppc64le.whl", hash = "sha256:34a7f768e3f985abdb42841e20e17b330ad3aaf4bb7e7aeeb73db2e70f077b99"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_s390x.manylinux_2_17_s390x.manylinux_2_28_s390x.whl", hash = "sha256:fb731e5deb0c7ef82d698b0f4c5bb724633ee2a489401594c5c88b02e6cb15f7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:257f26fed7d7ff59921b78244f3cd93ed2af1800ff048c33f624c87475819dd7"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:1ef99f0456d3d46a50945c98de1774da86f8e992ab5c77865ea8b8195341fc19"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:2c322db9c8c89009a990ef07c3bcc9f011a3269bc06782f916cd3d9eed7c9312"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:511729f456829ef86ac41ca78c63a5cb55240ed23b4b737faca0eb1abb1c41bc"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:88ab34806dea0671532d3f82d82b85e8fc23d7b2dd12fa837978dad9bb392a34"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win32.whl", hash = "sha256:16a8770207946ac75703458e2c743631c79c59c5890c80011d536248f8eaa432"}, + {file = "charset_normalizer-3.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:d22dbedd33326a4a5190dd4fe9e9e693ef12160c77382d9e87919bce54f3d4ca"}, + {file = "charset_normalizer-3.4.3-py3-none-any.whl", hash = "sha256:ce571ab16d890d23b5c278547ba694193a45011ff86a9162a71307ed9f86759a"}, + {file = "charset_normalizer-3.4.3.tar.gz", hash = "sha256:6fce4b8500244f6fcb71465d4a4930d132ba9ab8e71a7859e6a5d59851068d14"}, +] + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "docutils" +version = "0.20.1" +description = "Docutils -- Python Documentation Utilities" +optional = true +python-versions = ">=3.7" +files = [ + {file = "docutils-0.20.1-py3-none-any.whl", hash = "sha256:96f387a2c5562db4476f09f13bbab2192e764cac08ebbf3a34a95d9b1e4a59d6"}, + {file = "docutils-0.20.1.tar.gz", hash = "sha256:f08a4e276c3a1583a86dce3e34aba3fe04d02bba2dd51ed16106244e8a923e3b"}, +] + +[[package]] +name = "frozenlist" +version = "1.5.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = true +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, + {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, + {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, + {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, + {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, + {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, + {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, + {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, + {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, + {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, + {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, + {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, + {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, + {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, + {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, +] + +[[package]] +name = "h11" +version = "0.16.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.8" +files = [ + {file = "h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86"}, + {file = "h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1"}, +] + +[[package]] +name = "idna" +version = "3.10" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = true +python-versions = ">=3.6" +files = [ + {file = "idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3"}, + {file = "idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9"}, +] + +[package.extras] +all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = true +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "8.5.0" +description = "Read metadata from Python packages" +optional = true +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-8.5.0-py3-none-any.whl", hash = "sha256:45e54197d28b7a7f1559e60b95e7c567032b602131fbd588f1497f47880aa68b"}, + {file = "importlib_metadata-8.5.0.tar.gz", hash = "sha256:71522656f0abace1d072b9e5481a48f07c138e00f079c38c8f883823f9c26bd7"}, +] + +[package.dependencies] +zipp = ">=3.20" + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +perf = ["ipython"] +test = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6,!=8.1.*)", "pytest-perf (>=0.9.2)"] +type = ["pytest-mypy"] + +[[package]] +name = "jinja2" +version = "3.1.6" +description = "A very fast and expressive template engine." +optional = true +python-versions = ">=3.7" +files = [ + {file = "jinja2-3.1.6-py3-none-any.whl", hash = "sha256:85ece4451f492d0c13c5dd7c13a64681a86afae63a5f347908daf103ce6d2f67"}, + {file = "jinja2-3.1.6.tar.gz", hash = "sha256:0137fb05990d35f1275a587e9aee6d56da821fc83491a0fb838183be43f66d6d"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = true +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "multidict" +version = "6.1.0" +description = "multidict implementation" +optional = true +python-versions = ">=3.8" +files = [ + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, + {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, + {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, + {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, + {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, + {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, + {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, + {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, + {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, + {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, + {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, + {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, + {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, + {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "packaging" +version = "25.0" +description = "Core utilities for Python packages" +optional = true +python-versions = ">=3.8" +files = [ + {file = "packaging-25.0-py3-none-any.whl", hash = "sha256:29572ef2b1f17581046b3a2227d5c611fb25ec70ca1ba8554b24b0e69331a484"}, + {file = "packaging-25.0.tar.gz", hash = "sha256:d443872c98d677bf60f6a1f2f8c1cb748e8fe762d2bf9d3148b5599295b0fc4f"}, +] + +[[package]] +name = "propcache" +version = "0.2.0" +description = "Accelerated property cache" +optional = true +python-versions = ">=3.8" +files = [ + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, + {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, + {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, + {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, + {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, + {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, + {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, + {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, + {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, + {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, + {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, + {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, + {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, + {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, + {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, +] + +[[package]] +name = "pygments" +version = "2.19.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = true +python-versions = ">=3.8" +files = [ + {file = "pygments-2.19.2-py3-none-any.whl", hash = "sha256:86540386c03d588bb81d44bc3928634ff26449851e99741617ecb9037ee5ec0b"}, + {file = "pygments-2.19.2.tar.gz", hash = "sha256:636cb2477cec7f8952536970bc533bc43743542f70392ae026374600add5b887"}, +] + +[package.extras] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "python-engineio" +version = "4.12.2" +description = "Engine.IO server and client for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python_engineio-4.12.2-py3-none-any.whl", hash = "sha256:8218ab66950e179dfec4b4bbb30aecf3f5d86f5e58e6fc1aa7fde2c698b2804f"}, + {file = "python_engineio-4.12.2.tar.gz", hash = "sha256:e7e712ffe1be1f6a05ee5f951e72d434854a32fcfc7f6e4d9d3cae24ec70defa"}, +] + +[package.dependencies] +simple-websocket = ">=0.10.0" + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] +docs = ["sphinx"] + +[[package]] +name = "pytz" +version = "2025.2" +description = "World timezone definitions, modern and historical" +optional = true +python-versions = "*" +files = [ + {file = "pytz-2025.2-py2.py3-none-any.whl", hash = "sha256:5ddf76296dd8c44c26eb8f4b6f35488f3ccbf6fbbd7adee0b7262d43f0ec2f00"}, + {file = "pytz-2025.2.tar.gz", hash = "sha256:360b9e3dbb49a209c21ad61809c7fb453643e048b38924c765813546746e81c3"}, +] + +[[package]] +name = "requests" +version = "2.32.4" +description = "Python HTTP for Humans." +optional = true +python-versions = ">=3.8" +files = [ + {file = "requests-2.32.4-py3-none-any.whl", hash = "sha256:27babd3cda2a6d50b30443204ee89830707d396671944c998b5975b031ac2b2c"}, + {file = "requests-2.32.4.tar.gz", hash = "sha256:27d0316682c8a29834d3264820024b62a36942083d52caf2f14c0591336d3422"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset_normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "simple-websocket" +version = "1.1.0" +description = "Simple WebSocket server and client for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "simple_websocket-1.1.0-py3-none-any.whl", hash = "sha256:4af6069630a38ed6c561010f0e11a5bc0d4ca569b36306eb257cd9a192497c8c"}, + {file = "simple_websocket-1.1.0.tar.gz", hash = "sha256:7939234e7aa067c534abdab3a9ed933ec9ce4691b0713c78acb195560aa52ae4"}, +] + +[package.dependencies] +wsproto = "*" + +[package.extras] +dev = ["flake8", "pytest", "pytest-cov", "tox"] +docs = ["sphinx"] + +[[package]] +name = "snowballstemmer" +version = "3.0.1" +description = "This package provides 32 stemmers for 30 languages generated from Snowball algorithms." +optional = true +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*" +files = [ + {file = "snowballstemmer-3.0.1-py3-none-any.whl", hash = "sha256:6cd7b3897da8d6c9ffb968a6781fa6532dce9c3618a4b127d920dab764a19064"}, + {file = "snowballstemmer-3.0.1.tar.gz", hash = "sha256:6d5eeeec8e9f84d4d56b847692bacf79bc2c8e90c7f80ca4444ff8b6f2e52895"}, +] + +[[package]] +name = "sphinx" +version = "7.1.2" +description = "Python documentation generator" +optional = true +python-versions = ">=3.8" +files = [ + {file = "sphinx-7.1.2-py3-none-any.whl", hash = "sha256:d170a81825b2fcacb6dfd5a0d7f578a053e45d3f2b153fecc948c37344eb4cbe"}, + {file = "sphinx-7.1.2.tar.gz", hash = "sha256:780f4d32f1d7d1126576e0e5ecc19dc32ab76cd24e950228dcf7b1f6d3d9e22f"}, +] + +[package.dependencies] +alabaster = ">=0.7,<0.8" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.21" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.13" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.5" + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["docutils-stubs", "flake8 (>=3.5.0)", "flake8-simplify", "isort", "mypy (>=0.990)", "ruff", "sphinx-lint", "types-requests"] +test = ["cython", "filelock", "html5lib", "pytest (>=4.6)"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.4" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = true +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-applehelp-1.0.4.tar.gz", hash = "sha256:828f867945bbe39817c210a1abfd1bc4895c8b73fcaade56d45357a348a07d7e"}, + {file = "sphinxcontrib_applehelp-1.0.4-py3-none-any.whl", hash = "sha256:29d341f67fb0f6f586b23ad80e072c8e6ad0b48417db2bde114a4c9746feb228"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.2" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp document." +optional = true +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-devhelp-1.0.2.tar.gz", hash = "sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4"}, + {file = "sphinxcontrib_devhelp-1.0.2-py2.py3-none-any.whl", hash = "sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.1" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = true +python-versions = ">=3.8" +files = [ + {file = "sphinxcontrib-htmlhelp-2.0.1.tar.gz", hash = "sha256:0cbdd302815330058422b98a113195c9249825d681e18f11e8b1f78a2f11efff"}, + {file = "sphinxcontrib_htmlhelp-2.0.1-py3-none-any.whl", hash = "sha256:c38cb46dccf316c79de6e5515e1770414b797162b23cd3d06e67020e1d2a6903"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = true +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.3" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp document." +optional = true +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-qthelp-1.0.3.tar.gz", hash = "sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72"}, + {file = "sphinxcontrib_qthelp-1.0.3-py2.py3-none-any.whl", hash = "sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.5" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)." +optional = true +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-serializinghtml-1.1.5.tar.gz", hash = "sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952"}, + {file = "sphinxcontrib_serializinghtml-1.1.5-py2.py3-none-any.whl", hash = "sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +test = ["pytest"] + +[[package]] +name = "typing-extensions" +version = "4.13.2" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = true +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c"}, + {file = "typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef"}, +] + +[[package]] +name = "urllib3" +version = "2.2.3" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = true +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.3-py3-none-any.whl", hash = "sha256:ca899ca043dcb1bafa3e262d73aa25c465bfb49e0bd9dd5d59f1d0acba2f8fac"}, + {file = "urllib3-2.2.3.tar.gz", hash = "sha256:e7d814a81dad81e6caf2ec9fdedb284ecc9c73076b62654547cc64ccdcae26e9"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "websocket-client" +version = "1.8.0" +description = "WebSocket client for Python with low level API options" +optional = true +python-versions = ">=3.8" +files = [ + {file = "websocket_client-1.8.0-py3-none-any.whl", hash = "sha256:17b44cc997f5c498e809b22cdf2d9c7a9e71c02c8cc2b6c56e7c2d1239bfa526"}, + {file = "websocket_client-1.8.0.tar.gz", hash = "sha256:3239df9f44da632f96012472805d40a23281a991027ce11d2f45a6f24ac4c3da"}, +] + +[package.extras] +docs = ["Sphinx (>=6.0)", "myst-parser (>=2.0.0)", "sphinx-rtd-theme (>=1.1.0)"] +optional = ["python-socks", "wsaccel"] +test = ["websockets"] + +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + +[[package]] +name = "yarl" +version = "1.15.2" +description = "Yet another URL library" +optional = true +python-versions = ">=3.8" +files = [ + {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:e4ee8b8639070ff246ad3649294336b06db37a94bdea0d09ea491603e0be73b8"}, + {file = "yarl-1.15.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:a7cf963a357c5f00cb55b1955df8bbe68d2f2f65de065160a1c26b85a1e44172"}, + {file = "yarl-1.15.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:43ebdcc120e2ca679dba01a779333a8ea76b50547b55e812b8b92818d604662c"}, + {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3433da95b51a75692dcf6cc8117a31410447c75a9a8187888f02ad45c0a86c50"}, + {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:38d0124fa992dbacd0c48b1b755d3ee0a9f924f427f95b0ef376556a24debf01"}, + {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ded1b1803151dd0f20a8945508786d57c2f97a50289b16f2629f85433e546d47"}, + {file = "yarl-1.15.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ace4cad790f3bf872c082366c9edd7f8f8f77afe3992b134cfc810332206884f"}, + {file = "yarl-1.15.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c77494a2f2282d9bbbbcab7c227a4d1b4bb829875c96251f66fb5f3bae4fb053"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:b7f227ca6db5a9fda0a2b935a2ea34a7267589ffc63c8045f0e4edb8d8dcf956"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:31561a5b4d8dbef1559b3600b045607cf804bae040f64b5f5bca77da38084a8a"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3e52474256a7db9dcf3c5f4ca0b300fdea6c21cca0148c8891d03a025649d935"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:0e1af74a9529a1137c67c887ed9cde62cff53aa4d84a3adbec329f9ec47a3936"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:15c87339490100c63472a76d87fe7097a0835c705eb5ae79fd96e343473629ed"}, + {file = "yarl-1.15.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:74abb8709ea54cc483c4fb57fb17bb66f8e0f04438cff6ded322074dbd17c7ec"}, + {file = "yarl-1.15.2-cp310-cp310-win32.whl", hash = "sha256:ffd591e22b22f9cb48e472529db6a47203c41c2c5911ff0a52e85723196c0d75"}, + {file = "yarl-1.15.2-cp310-cp310-win_amd64.whl", hash = "sha256:1695497bb2a02a6de60064c9f077a4ae9c25c73624e0d43e3aa9d16d983073c2"}, + {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9fcda20b2de7042cc35cf911702fa3d8311bd40055a14446c1e62403684afdc5"}, + {file = "yarl-1.15.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0545de8c688fbbf3088f9e8b801157923be4bf8e7b03e97c2ecd4dfa39e48e0e"}, + {file = "yarl-1.15.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:fbda058a9a68bec347962595f50546a8a4a34fd7b0654a7b9697917dc2bf810d"}, + {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d1ac2bc069f4a458634c26b101c2341b18da85cb96afe0015990507efec2e417"}, + {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cd126498171f752dd85737ab1544329a4520c53eed3997f9b08aefbafb1cc53b"}, + {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3db817b4e95eb05c362e3b45dafe7144b18603e1211f4a5b36eb9522ecc62bcf"}, + {file = "yarl-1.15.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:076b1ed2ac819933895b1a000904f62d615fe4533a5cf3e052ff9a1da560575c"}, + {file = "yarl-1.15.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f8cfd847e6b9ecf9f2f2531c8427035f291ec286c0a4944b0a9fce58c6446046"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:32b66be100ac5739065496c74c4b7f3015cef792c3174982809274d7e51b3e04"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:34a2d76a1984cac04ff8b1bfc939ec9dc0914821264d4a9c8fd0ed6aa8d4cfd2"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:0afad2cd484908f472c8fe2e8ef499facee54a0a6978be0e0cff67b1254fd747"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c68e820879ff39992c7f148113b46efcd6ec765a4865581f2902b3c43a5f4bbb"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:98f68df80ec6ca3015186b2677c208c096d646ef37bbf8b49764ab4a38183931"}, + {file = "yarl-1.15.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:3c56ec1eacd0a5d35b8a29f468659c47f4fe61b2cab948ca756c39b7617f0aa5"}, + {file = "yarl-1.15.2-cp311-cp311-win32.whl", hash = "sha256:eedc3f247ee7b3808ea07205f3e7d7879bc19ad3e6222195cd5fbf9988853e4d"}, + {file = "yarl-1.15.2-cp311-cp311-win_amd64.whl", hash = "sha256:0ccaa1bc98751fbfcf53dc8dfdb90d96e98838010fc254180dd6707a6e8bb179"}, + {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:82d5161e8cb8f36ec778fd7ac4d740415d84030f5b9ef8fe4da54784a1f46c94"}, + {file = "yarl-1.15.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:fa2bea05ff0a8fb4d8124498e00e02398f06d23cdadd0fe027d84a3f7afde31e"}, + {file = "yarl-1.15.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:99e12d2bf587b44deb74e0d6170fec37adb489964dbca656ec41a7cd8f2ff178"}, + {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:243fbbbf003754fe41b5bdf10ce1e7f80bcc70732b5b54222c124d6b4c2ab31c"}, + {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:856b7f1a7b98a8c31823285786bd566cf06226ac4f38b3ef462f593c608a9bd6"}, + {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:553dad9af802a9ad1a6525e7528152a015b85fb8dbf764ebfc755c695f488367"}, + {file = "yarl-1.15.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:30c3ff305f6e06650a761c4393666f77384f1cc6c5c0251965d6bfa5fbc88f7f"}, + {file = "yarl-1.15.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:353665775be69bbfc6d54c8d134bfc533e332149faeddd631b0bc79df0897f46"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f4fe99ce44128c71233d0d72152db31ca119711dfc5f2c82385ad611d8d7f897"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:9c1e3ff4b89cdd2e1a24c214f141e848b9e0451f08d7d4963cb4108d4d798f1f"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:711bdfae4e699a6d4f371137cbe9e740dc958530cb920eb6f43ff9551e17cfbc"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:4388c72174868884f76affcdd3656544c426407e0043c89b684d22fb265e04a5"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:f0e1844ad47c7bd5d6fa784f1d4accc5f4168b48999303a868fe0f8597bde715"}, + {file = "yarl-1.15.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a5cafb02cf097a82d74403f7e0b6b9df3ffbfe8edf9415ea816314711764a27b"}, + {file = "yarl-1.15.2-cp312-cp312-win32.whl", hash = "sha256:156ececdf636143f508770bf8a3a0498de64da5abd890c7dbb42ca9e3b6c05b8"}, + {file = "yarl-1.15.2-cp312-cp312-win_amd64.whl", hash = "sha256:435aca062444a7f0c884861d2e3ea79883bd1cd19d0a381928b69ae1b85bc51d"}, + {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:416f2e3beaeae81e2f7a45dc711258be5bdc79c940a9a270b266c0bec038fb84"}, + {file = "yarl-1.15.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:173563f3696124372831007e3d4b9821746964a95968628f7075d9231ac6bb33"}, + {file = "yarl-1.15.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:9ce2e0f6123a60bd1a7f5ae3b2c49b240c12c132847f17aa990b841a417598a2"}, + {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eaea112aed589131f73d50d570a6864728bd7c0c66ef6c9154ed7b59f24da611"}, + {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4ca3b9f370f218cc2a0309542cab8d0acdfd66667e7c37d04d617012485f904"}, + {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:23ec1d3c31882b2a8a69c801ef58ebf7bae2553211ebbddf04235be275a38548"}, + {file = "yarl-1.15.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:75119badf45f7183e10e348edff5a76a94dc19ba9287d94001ff05e81475967b"}, + {file = "yarl-1.15.2-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:78e6fdc976ec966b99e4daa3812fac0274cc28cd2b24b0d92462e2e5ef90d368"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:8657d3f37f781d987037f9cc20bbc8b40425fa14380c87da0cb8dfce7c92d0fb"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:93bed8a8084544c6efe8856c362af08a23e959340c87a95687fdbe9c9f280c8b"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:69d5856d526802cbda768d3e6246cd0d77450fa2a4bc2ea0ea14f0d972c2894b"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:ccad2800dfdff34392448c4bf834be124f10a5bc102f254521d931c1c53c455a"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:a880372e2e5dbb9258a4e8ff43f13888039abb9dd6d515f28611c54361bc5644"}, + {file = "yarl-1.15.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:c998d0558805860503bc3a595994895ca0f7835e00668dadc673bbf7f5fbfcbe"}, + {file = "yarl-1.15.2-cp313-cp313-win32.whl", hash = "sha256:533a28754e7f7439f217550a497bb026c54072dbe16402b183fdbca2431935a9"}, + {file = "yarl-1.15.2-cp313-cp313-win_amd64.whl", hash = "sha256:5838f2b79dc8f96fdc44077c9e4e2e33d7089b10788464609df788eb97d03aad"}, + {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:fbbb63bed5fcd70cd3dd23a087cd78e4675fb5a2963b8af53f945cbbca79ae16"}, + {file = "yarl-1.15.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e2e93b88ecc8f74074012e18d679fb2e9c746f2a56f79cd5e2b1afcf2a8a786b"}, + {file = "yarl-1.15.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af8ff8d7dc07ce873f643de6dfbcd45dc3db2c87462e5c387267197f59e6d776"}, + {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:66f629632220a4e7858b58e4857927dd01a850a4cef2fb4044c8662787165cf7"}, + {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:833547179c31f9bec39b49601d282d6f0ea1633620701288934c5f66d88c3e50"}, + {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2aa738e0282be54eede1e3f36b81f1e46aee7ec7602aa563e81e0e8d7b67963f"}, + {file = "yarl-1.15.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9a13a07532e8e1c4a5a3afff0ca4553da23409fad65def1b71186fb867eeae8d"}, + {file = "yarl-1.15.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c45817e3e6972109d1a2c65091504a537e257bc3c885b4e78a95baa96df6a3f8"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:670eb11325ed3a6209339974b276811867defe52f4188fe18dc49855774fa9cf"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:d417a4f6943112fae3924bae2af7112562285848d9bcee737fc4ff7cbd450e6c"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:bc8936d06cd53fddd4892677d65e98af514c8d78c79864f418bbf78a4a2edde4"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:954dde77c404084c2544e572f342aef384240b3e434e06cecc71597e95fd1ce7"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:5bc0df728e4def5e15a754521e8882ba5a5121bd6b5a3a0ff7efda5d6558ab3d"}, + {file = "yarl-1.15.2-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:b71862a652f50babab4a43a487f157d26b464b1dedbcc0afda02fd64f3809d04"}, + {file = "yarl-1.15.2-cp38-cp38-win32.whl", hash = "sha256:63eab904f8630aed5a68f2d0aeab565dcfc595dc1bf0b91b71d9ddd43dea3aea"}, + {file = "yarl-1.15.2-cp38-cp38-win_amd64.whl", hash = "sha256:2cf441c4b6e538ba0d2591574f95d3fdd33f1efafa864faa077d9636ecc0c4e9"}, + {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:a32d58f4b521bb98b2c0aa9da407f8bd57ca81f34362bcb090e4a79e9924fefc"}, + {file = "yarl-1.15.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:766dcc00b943c089349d4060b935c76281f6be225e39994c2ccec3a2a36ad627"}, + {file = "yarl-1.15.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:bed1b5dbf90bad3bfc19439258c97873eab453c71d8b6869c136346acfe497e7"}, + {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed20a4bdc635f36cb19e630bfc644181dd075839b6fc84cac51c0f381ac472e2"}, + {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d538df442c0d9665664ab6dd5fccd0110fa3b364914f9c85b3ef9b7b2e157980"}, + {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c6cf1d92edf936ceedc7afa61b07e9d78a27b15244aa46bbcd534c7458ee1b"}, + {file = "yarl-1.15.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce44217ad99ffad8027d2fde0269ae368c86db66ea0571c62a000798d69401fb"}, + {file = "yarl-1.15.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b47a6000a7e833ebfe5886b56a31cb2ff12120b1efd4578a6fcc38df16cc77bd"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:e52f77a0cd246086afde8815039f3e16f8d2be51786c0a39b57104c563c5cbb0"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:f9ca0e6ce7774dc7830dc0cc4bb6b3eec769db667f230e7c770a628c1aa5681b"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:136f9db0f53c0206db38b8cd0c985c78ded5fd596c9a86ce5c0b92afb91c3a19"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:173866d9f7409c0fb514cf6e78952e65816600cb888c68b37b41147349fe0057"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:6e840553c9c494a35e449a987ca2c4f8372668ee954a03a9a9685075228e5036"}, + {file = "yarl-1.15.2-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:458c0c65802d816a6b955cf3603186de79e8fdb46d4f19abaec4ef0a906f50a7"}, + {file = "yarl-1.15.2-cp39-cp39-win32.whl", hash = "sha256:5b48388ded01f6f2429a8c55012bdbd1c2a0c3735b3e73e221649e524c34a58d"}, + {file = "yarl-1.15.2-cp39-cp39-win_amd64.whl", hash = "sha256:81dadafb3aa124f86dc267a2168f71bbd2bfb163663661ab0038f6e4b8edb810"}, + {file = "yarl-1.15.2-py3-none-any.whl", hash = "sha256:0d3105efab7c5c091609abacad33afff33bdff0035bece164c98bcf5a85ef90a"}, + {file = "yarl-1.15.2.tar.gz", hash = "sha256:a39c36f4218a5bb668b4f06874d676d35a035ee668e6e7e3538835c703634b84"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.0" + +[[package]] +name = "zipp" +version = "3.20.2" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = true +python-versions = ">=3.8" +files = [ + {file = "zipp-3.20.2-py3-none-any.whl", hash = "sha256:a817ac80d6cf4b23bf7f2828b7cabf326f15a001bea8b1f9b49631780ba28350"}, + {file = "zipp-3.20.2.tar.gz", hash = "sha256:bc9eb26f4506fda01b81bcde0ca78103b6e62f991b381fec825435c836edbc29"}, +] + +[package.extras] +check = ["pytest-checkdocs (>=2.4)", "pytest-ruff (>=0.2.1)"] +cover = ["pytest-cov"] +doc = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +enabler = ["pytest-enabler (>=2.2)"] +test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] +type = ["pytest-mypy"] + +[extras] +asyncio-client = ["aiohttp"] +client = ["requests", "websocket-client"] +docs = ["sphinx"] + +[metadata] +lock-version = "2.0" +python-versions = ">=3.8" +content-hash = "5e7c85a2b41b3490005e4fad25f408eccfa02551facdd10fa57910f27749ae5e" diff --git a/pyproject.toml b/pyproject.toml index e7b6f65..10b773f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,21 +1,17 @@ -[project] -name = "python-socketio" +[tool.poetry] +name = "fast-socketio" version = "5.13.1.dev0" -license = {text = "MIT"} -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", - "Operating System :: OS Independent", +description = "light and typization Socket.IO server and client for Python" +license = "MIT" +readme = "README.md" +homepage = "https://github.com/cicwak/fast-socketio" +repository = "https://github.com/cicwak/fast-socketio" +packages = [ + { include = "socketio", from = "src" }, ] -requires-python = ">=3.8" -dependencies = [ - "bidict >= 0.21.0", - "python-engineio >= 4.11.0", +authors = [ + "Miguel Grinberg ", + "Konstantin Ponomarev ", ] [project.readme] @@ -23,8 +19,8 @@ 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" +Homepage = "https://github.com/cicwak/fast-socketio" +#"Bug Tracker" = "https://github.com/miguelgrinberg/python-socketio/issues" [project.optional-dependencies] client = [ @@ -38,6 +34,20 @@ docs = [ "sphinx", ] +[tool.poetry.dependencies] +python = ">=3.8" +bidict = ">=0.21.0" +python-engineio = "^4.12.2" +requests = { version = ">=2.21.0", optional = true } +websocket-client = { version = ">=0.54.0", optional = true } +aiohttp = { version = ">=3.4", optional = true } +sphinx = { version = "*", optional = true } + +[tool.poetry.extras] +client = ["requests", "websocket-client"] +asyncio_client = ["aiohttp"] +docs = ["sphinx"] + [tool.setuptools] zip-safe = false include-package-data = true @@ -58,3 +68,61 @@ build-backend = "setuptools.build_meta" [tool.pytest.ini_options] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "session" + +[tool.mypy] +plugins = ["pydantic.mypy"] +ignore_missing_imports = true +disallow_untyped_calls = false +exclude = "(venv|migrations|start_service\\.dist|start_service\\.build|start_service\\.onefile-build)" + +[tool.ruff] +line-length = 88 +exclude = [ + "migrations", + "venv", + "start_service.dist", + "start_service.build", + "start_service.onefile-build", +] + + +[tool.ruff.lint] +select = [ + "F", "E", "W", "I", "UP", "N", "Q", + "B", "BLE", "ARG", "SIM", "C4", "PERF", + "A", "DTZ", "TCH", "TRY", + "RUF", "D", "ANN", + "RET", "RSE", "TID", +] +# Столкновения стилей докстрингов: выберем Google и заглушим конфликтующие правила +ignore = ["D203", "D213"] + +[tool.ruff.lint.pydocstyle] +convention = "google" + +[tool.ruff.lint.isort] +known-first-party = ["fast_socketio"] +combine-as-imports = true + +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "parents" + +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint.pep8-naming] +classmethod-decorators = [ + "pydantic.validator", + "declared_attr", +] + +[tool.ruff.lint.mccabe] +max-complexity = 10 + +[tool.ruff.lint.per-file-ignores] +# Тестам позволяем послабления: assert, отсутствие докстрингов и Any +"tests/**" = ["S101", "D", "ANN401", "ARG002", "PT009", "PT011"] +# В __init__ обычно реэкспорт, без докстрингов — не ругаемся +"**/__init__.py" = ["D104", "F401"] +# Скрипты/пример использования (если есть): можно Any и без докстрингов +"examples/**" = ["D", "ANN401"] diff --git a/src/socketio/__init__.py b/src/socketio/__init__.py index 95642f4..5e74bf2 100644 --- a/src/socketio/__init__.py +++ b/src/socketio/__init__.py @@ -1,28 +1,46 @@ +from .asgi import ASGIApp +from .async_aiopika_manager import AsyncAioPikaManager +from .async_client import AsyncClient +from .async_manager import AsyncManager +from .async_namespace import AsyncClientNamespace, AsyncNamespace +from .async_redis_manager import AsyncRedisManager +from .async_server import AsyncServer +from .async_simple_client import AsyncSimpleClient from .client import Client -from .simple_client import SimpleClient +from .kafka_manager import KafkaManager +from .kombu_manager import KombuManager from .manager import Manager +from .middleware import Middleware, WSGIApp +from .namespace import ClientNamespace, Namespace from .pubsub_manager import PubSubManager -from .kombu_manager import KombuManager from .redis_manager import RedisManager -from .kafka_manager import KafkaManager -from .zmq_manager import ZmqManager from .server import Server -from .namespace import Namespace, ClientNamespace -from .middleware import WSGIApp, Middleware +from .simple_client import SimpleClient from .tornado import get_tornado_handler -from .async_client import AsyncClient -from .async_simple_client import AsyncSimpleClient -from .async_server import AsyncServer -from .async_manager import AsyncManager -from .async_namespace import AsyncNamespace, AsyncClientNamespace -from .async_redis_manager import AsyncRedisManager -from .async_aiopika_manager import AsyncAioPikaManager -from .asgi import ASGIApp +from .zmq_manager import ZmqManager -__all__ = ['SimpleClient', 'Client', 'Server', 'Manager', 'PubSubManager', - 'KombuManager', 'RedisManager', 'ZmqManager', 'KafkaManager', - 'Namespace', 'ClientNamespace', 'WSGIApp', 'Middleware', - 'AsyncSimpleClient', 'AsyncClient', 'AsyncServer', - 'AsyncNamespace', 'AsyncClientNamespace', 'AsyncManager', - 'AsyncRedisManager', 'ASGIApp', 'get_tornado_handler', - 'AsyncAioPikaManager'] +__all__ = [ + "ASGIApp", + "AsyncAioPikaManager", + "AsyncClient", + "AsyncClientNamespace", + "AsyncManager", + "AsyncNamespace", + "AsyncRedisManager", + "AsyncServer", + "AsyncSimpleClient", + "Client", + "ClientNamespace", + "KafkaManager", + "KombuManager", + "Manager", + "Middleware", + "Namespace", + "PubSubManager", + "RedisManager", + "Server", + "SimpleClient", + "WSGIApp", + "ZmqManager", + "get_tornado_handler", +] diff --git a/src/socketio/admin.py b/src/socketio/admin.py index 12b905e..ed3fb72 100644 --- a/src/socketio/admin.py +++ b/src/socketio/admin.py @@ -1,9 +1,10 @@ -from datetime import datetime, timezone import functools import os import socket import time +from datetime import datetime, timezone from urllib.parse import parse_qs + from .exceptions import ConnectionRefusedError HOSTNAME = socket.gethostname() @@ -16,15 +17,15 @@ class EventBuffer: def push(self, type, count=1): timestamp = int(time.time()) * 1000 - key = f'{timestamp};{type}' + key = f"{timestamp};{type}" if key not in self.buffer: self.buffer[key] = { - 'timestamp': timestamp, - 'type': type, - 'count': count, + "timestamp": timestamp, + "type": type, + "count": count, } else: - self.buffer[key]['count'] += count + self.buffer[key]["count"] += count def get_and_clear(self): buffer = self.buffer @@ -33,19 +34,28 @@ class EventBuffer: class InstrumentedServer: - def __init__(self, sio, auth=None, mode='development', read_only=False, - server_id=None, namespace='/admin', server_stats_interval=2): + def __init__( + self, + sio, + auth=None, + mode="development", + read_only=False, + server_id=None, + namespace="/admin", + server_stats_interval=2, + ): """Instrument the Socket.IO server for monitoring with the `Socket.IO Admin UI `_. """ if auth is None: - raise ValueError('auth must be specified') + raise ValueError("auth must be specified") self.sio = sio self.auth = auth self.admin_namespace = namespace self.read_only = read_only self.server_id = server_id or ( - self.sio.manager.host_id if hasattr(self.sio.manager, 'host_id') + self.sio.manager.host_id + if hasattr(self.sio.manager, "host_id") else HOSTNAME ) self.mode = mode @@ -60,19 +70,20 @@ class InstrumentedServer: self.instrument() def instrument(self): - self.sio.on('connect', self.admin_connect, - namespace=self.admin_namespace) + self.sio.on("connect", self.admin_connect, namespace=self.admin_namespace) - if self.mode == 'development': + if self.mode == "development": if not self.read_only: # pragma: no branch - self.sio.on('emit', self.admin_emit, - namespace=self.admin_namespace) - self.sio.on('join', self.admin_enter_room, - namespace=self.admin_namespace) - self.sio.on('leave', self.admin_leave_room, - namespace=self.admin_namespace) - self.sio.on('_disconnect', self.admin_disconnect, - namespace=self.admin_namespace) + self.sio.on("emit", self.admin_emit, namespace=self.admin_namespace) + self.sio.on( + "join", self.admin_enter_room, namespace=self.admin_namespace + ) + self.sio.on( + "leave", self.admin_leave_room, namespace=self.admin_namespace + ) + self.sio.on( + "_disconnect", self.admin_disconnect, namespace=self.admin_namespace + ) # track socket connection times self.sio.manager._timestamps = {} @@ -82,13 +93,11 @@ class InstrumentedServer: self.sio._trigger_event = self._trigger_event # report join rooms - self.sio.manager.__basic_enter_room = \ - self.sio.manager.basic_enter_room + self.sio.manager.__basic_enter_room = self.sio.manager.basic_enter_room self.sio.manager.basic_enter_room = self._basic_enter_room # report leave rooms - self.sio.manager.__basic_leave_room = \ - self.sio.manager.basic_leave_room + self.sio.manager.__basic_leave_room = self.sio.manager.basic_leave_room self.sio.manager.basic_leave_room = self._basic_leave_room # report emit events @@ -96,42 +105,45 @@ class InstrumentedServer: self.sio.manager.emit = self._emit # report engine.io connections - self.sio.eio.on('connect', self._handle_eio_connect) - self.sio.eio.on('disconnect', self._handle_eio_disconnect) + self.sio.eio.on("connect", self._handle_eio_connect) + self.sio.eio.on("disconnect", self._handle_eio_disconnect) # report polling packets from engineio.socket import Socket + self.sio.eio.__ok = self.sio.eio._ok self.sio.eio._ok = self._eio_http_response Socket.__handle_post_request = Socket.handle_post_request Socket.handle_post_request = functools.partialmethod( - self.__class__._eio_handle_post_request, self) + self.__class__._eio_handle_post_request, self + ) # report websocket packets Socket.__websocket_handler = Socket._websocket_handler Socket._websocket_handler = functools.partialmethod( - self.__class__._eio_websocket_handler, self) + self.__class__._eio_websocket_handler, self + ) # report connected sockets with each ping - if self.mode == 'development': + if self.mode == "development": Socket.__send_ping = Socket._send_ping Socket._send_ping = functools.partialmethod( - self.__class__._eio_send_ping, self) + self.__class__._eio_send_ping, self + ) def uninstrument(self): # pragma: no cover - if self.mode == 'development': + if self.mode == "development": 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.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.eio._ok = self.sio.eio.__ok from engineio.socket import Socket + Socket.handle_post_request = Socket.__handle_post_request Socket._websocket_handler = Socket.__websocket_handler - if self.mode == 'development': + if self.mode == "development": Socket._send_ping = Socket.__send_ping def admin_connect(self, sid, environ, client_auth): @@ -144,31 +156,41 @@ class InstrumentedServer: else: authenticated = self.auth(client_auth) if not authenticated: - raise ConnectionRefusedError('authentication failed') + raise ConnectionRefusedError("authentication failed") def config(sid): self.sio.sleep(0.1) # supported features - features = ['AGGREGATED_EVENTS'] + features = ["AGGREGATED_EVENTS"] if not self.read_only: - features += ['EMIT', 'JOIN', 'LEAVE', 'DISCONNECT', 'MJOIN', - 'MLEAVE', 'MDISCONNECT'] - if self.mode == 'development': - features.append('ALL_EVENTS') - self.sio.emit('config', {'supportedFeatures': features}, - to=sid, namespace=self.admin_namespace) + features += [ + "EMIT", + "JOIN", + "LEAVE", + "DISCONNECT", + "MJOIN", + "MLEAVE", + "MDISCONNECT", + ] + if self.mode == "development": + features.append("ALL_EVENTS") + self.sio.emit( + "config", + {"supportedFeatures": features}, + to=sid, + namespace=self.admin_namespace, + ) # send current sockets - if self.mode == 'development': + if self.mode == "development": all_sockets = [] for nsp in self.sio.manager.get_namespaces(): - for sid, eio_sid in self.sio.manager.get_participants( - nsp, None): - all_sockets.append( - self.serialize_socket(sid, nsp, eio_sid)) - self.sio.emit('all_sockets', all_sockets, to=sid, - namespace=self.admin_namespace) + for sid, eio_sid in self.sio.manager.get_participants(nsp, None): + all_sockets.append(self.serialize_socket(sid, nsp, eio_sid)) + self.sio.emit( + "all_sockets", all_sockets, to=sid, namespace=self.admin_namespace + ) self.sio.start_background_task(config, sid) @@ -176,18 +198,15 @@ class InstrumentedServer: self.sio.emit(event, data, to=room_filter, namespace=namespace) def admin_enter_room(self, _, namespace, room, room_filter=None): - for sid, _ in self.sio.manager.get_participants( - namespace, room_filter): + for sid, _ in self.sio.manager.get_participants(namespace, room_filter): self.sio.enter_room(sid, room, namespace=namespace) def admin_leave_room(self, _, namespace, room, room_filter=None): - for sid, _ in self.sio.manager.get_participants( - namespace, room_filter): + for sid, _ in self.sio.manager.get_participants(namespace, room_filter): self.sio.leave_room(sid, room, namespace=namespace) def admin_disconnect(self, _, namespace, close, room_filter=None): - for sid, _ in self.sio.manager.get_participants( - namespace, room_filter): + for sid, _ in self.sio.manager.get_participants(namespace, room_filter): self.sio.disconnect(sid, namespace=namespace) def shutdown(self): @@ -198,30 +217,42 @@ class InstrumentedServer: def _trigger_event(self, event, namespace, *args): t = time.time() sid = args[0] - if event == 'connect': + 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.fromtimestamp(t, timezone.utc).isoformat(), - ), namespace=self.admin_namespace) - elif event == 'disconnect': + self.sio.emit( + "socket_connected", + ( + serialized_socket, + datetime.fromtimestamp(t, timezone.utc).isoformat(), + ), + 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.fromtimestamp(t, timezone.utc).isoformat(), - ), namespace=self.admin_namespace) + self.sio.emit( + "socket_disconnected", + ( + namespace, + sid, + reason, + datetime.fromtimestamp(t, timezone.utc).isoformat(), + ), + namespace=self.admin_namespace, + ) else: - self.sio.emit('event_received', ( - namespace, - sid, - (event, *args[1:]), - datetime.fromtimestamp(t, timezone.utc).isoformat(), - ), namespace=self.admin_namespace) + self.sio.emit( + "event_received", + ( + namespace, + sid, + (event, *args[1:]), + datetime.fromtimestamp(t, timezone.utc).isoformat(), + ), + namespace=self.admin_namespace, + ) return self.sio.__trigger_event(event, namespace, *args) def _check_for_upgrade(self, eio_sid, sid, namespace): # pragma: no cover @@ -229,94 +260,116 @@ class InstrumentedServer: self.sio.sleep(5) try: if self.sio.eio._get_socket(eio_sid).upgraded: - self.sio.emit('socket_updated', { - 'id': sid, - 'nsp': namespace, - 'transport': 'websocket', - }, namespace=self.admin_namespace) + self.sio.emit( + "socket_updated", + { + "id": sid, + "nsp": namespace, + "transport": "websocket", + }, + namespace=self.admin_namespace, + ) break except KeyError: pass def _basic_enter_room(self, sid, namespace, room, eio_sid=None): - ret = self.sio.manager.__basic_enter_room(sid, namespace, room, - eio_sid) + ret = self.sio.manager.__basic_enter_room(sid, namespace, room, eio_sid) if room: - self.sio.emit('room_joined', ( - namespace, - room, - sid, - datetime.now(timezone.utc).isoformat(), - ), namespace=self.admin_namespace) + self.sio.emit( + "room_joined", + ( + namespace, + room, + sid, + datetime.now(timezone.utc).isoformat(), + ), + namespace=self.admin_namespace, + ) return ret def _basic_leave_room(self, sid, namespace, room): if room: - self.sio.emit('room_left', ( - namespace, - room, - sid, - datetime.now(timezone.utc).isoformat(), - ), namespace=self.admin_namespace) + self.sio.emit( + "room_left", + ( + namespace, + room, + sid, + datetime.now(timezone.utc).isoformat(), + ), + namespace=self.admin_namespace, + ) return self.sio.manager.__basic_leave_room(sid, namespace, room) - def _emit(self, event, data, namespace, room=None, skip_sid=None, - callback=None, **kwargs): - ret = self.sio.manager.__emit(event, data, namespace, room=room, - skip_sid=skip_sid, callback=callback, - **kwargs) + def _emit( + self, event, data, namespace, room=None, skip_sid=None, callback=None, **kwargs + ): + ret = self.sio.manager.__emit( + event, + data, + namespace, + room=room, + skip_sid=skip_sid, + callback=callback, + **kwargs, + ) if namespace != self.admin_namespace: - event_data = [event] + list(data) if isinstance(data, tuple) \ - else [event, data] + event_data = ( + [event] + list(data) if isinstance(data, tuple) 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): if sid not in skip_sid: - self.sio.emit('event_sent', ( - namespace, - sid, - event_data, - datetime.now(timezone.utc).isoformat(), - ), namespace=self.admin_namespace) + self.sio.emit( + "event_sent", + ( + namespace, + sid, + event_data, + datetime.now(timezone.utc).isoformat(), + ), + 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() - self.stats_task = self.sio.start_background_task( - self._emit_server_stats) + self.stats_task = self.sio.start_background_task(self._emit_server_stats) - self.event_buffer.push('rawConnection') + self.event_buffer.push("rawConnection") return self.sio._handle_eio_connect(eio_sid, environ) def _handle_eio_disconnect(self, eio_sid, reason): - self.event_buffer.push('rawDisconnection') + self.event_buffer.push("rawDisconnection") 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, - jsonp_index=jsonp_index) - self.event_buffer.push('packetsOut') - self.event_buffer.push('bytesOut', len(ret['response'])) + ret = self.sio.eio.__ok( + packets=packets, headers=headers, jsonp_index=jsonp_index + ) + self.event_buffer.push("packetsOut") + self.event_buffer.push("bytesOut", len(ret["response"])) return ret def _eio_handle_post_request(socket, self, environ): ret = socket.__handle_post_request(environ) - self.event_buffer.push('packetsIn') - self.event_buffer.push( - 'bytesIn', int(environ.get('CONTENT_LENGTH', 0))) + self.event_buffer.push("packetsIn") + self.event_buffer.push("bytesIn", int(environ.get("CONTENT_LENGTH", 0))) return ret def _eio_websocket_handler(socket, self, ws): def _send(ws, data, *args, **kwargs): - self.event_buffer.push('packetsOut') - self.event_buffer.push('bytesOut', len(data)) + self.event_buffer.push("packetsOut") + self.event_buffer.push("bytesOut", len(data)) return ws.__send(data, *args, **kwargs) def _wait(ws): ret = ws.__wait() - self.event_buffer.push('packetsIn') - self.event_buffer.push('bytesIn', len(ret or '')) + self.event_buffer.push("packetsIn") + self.event_buffer.push("bytesIn", len(ret or "")) return ret ws.__send = ws.send @@ -331,12 +384,15 @@ class InstrumentedServer: for namespace in self.sio.manager.get_namespaces(): sid = self.sio.manager.sid_from_eio_sid(eio_sid, namespace) if sid: - serialized_socket = self.serialize_socket(sid, namespace, - eio_sid) - self.sio.emit('socket_connected', ( - serialized_socket, - datetime.fromtimestamp(t, timezone.utc).isoformat(), - ), namespace=self.admin_namespace) + serialized_socket = self.serialize_socket(sid, namespace, eio_sid) + self.sio.emit( + "socket_connected", + ( + serialized_socket, + datetime.fromtimestamp(t, timezone.utc).isoformat(), + ), + namespace=self.admin_namespace, + ) return socket.__send_ping() def _emit_server_stats(self): @@ -345,47 +401,66 @@ class InstrumentedServer: namespaces.sort() while not self.stop_stats_event.is_set(): self.sio.sleep(self.server_stats_interval) - self.sio.emit('server_stats', { - 'serverId': self.server_id, - 'hostname': HOSTNAME, - 'pid': PID, - 'uptime': time.time() - start_time, - 'clientsCount': len(self.sio.eio.sockets), - 'pollingClientsCount': len( - [s for s in self.sio.eio.sockets.values() - if not s.upgraded]), - 'aggregatedEvents': self.event_buffer.get_and_clear(), - 'namespaces': [{ - 'name': nsp, - 'socketsCount': len(self.sio.manager.rooms.get( - nsp, {None: []}).get(None, [])) - } for nsp in namespaces], - }, namespace=self.admin_namespace) + self.sio.emit( + "server_stats", + { + "serverId": self.server_id, + "hostname": HOSTNAME, + "pid": PID, + "uptime": time.time() - start_time, + "clientsCount": len(self.sio.eio.sockets), + "pollingClientsCount": len( + [s for s in self.sio.eio.sockets.values() if not s.upgraded] + ), + "aggregatedEvents": self.event_buffer.get_and_clear(), + "namespaces": [ + { + "name": nsp, + "socketsCount": len( + self.sio.manager.rooms.get(nsp, {None: []}).get( + None, [] + ) + ), + } + for nsp in namespaces + ], + }, + namespace=self.admin_namespace, + ) def serialize_socket(self, sid, namespace, eio_sid=None): if eio_sid is None: # pragma: no cover eio_sid = self.sio.manager.eio_sid_from_sid(sid) socket = self.sio.eio._get_socket(eio_sid) environ = self.sio.environ.get(eio_sid, {}) - tm = self.sio.manager._timestamps[sid] if sid in \ - self.sio.manager._timestamps else 0 + tm = ( + self.sio.manager._timestamps[sid] + if sid in self.sio.manager._timestamps + else 0 + ) return { - 'id': sid, - 'clientId': eio_sid, - 'transport': 'websocket' if socket.upgraded else 'polling', - 'nsp': namespace, - 'data': {}, - 'handshake': { - 'address': environ.get('REMOTE_ADDR', ''), - 'headers': {k[5:].lower(): v for k, v in environ.items() - if k.startswith('HTTP_')}, - 'query': {k: v[0] if len(v) == 1 else v for k, v in parse_qs( - environ.get('QUERY_STRING', '')).items()}, - 'secure': environ.get('wsgi.url_scheme', '') == 'https', - 'url': environ.get('PATH_INFO', ''), - 'issued': tm * 1000, - 'time': datetime.fromtimestamp(tm, timezone.utc).isoformat() - if tm else '', + "id": sid, + "clientId": eio_sid, + "transport": "websocket" if socket.upgraded else "polling", + "nsp": namespace, + "data": {}, + "handshake": { + "address": environ.get("REMOTE_ADDR", ""), + "headers": { + k[5:].lower(): v + for k, v in environ.items() + if k.startswith("HTTP_") + }, + "query": { + k: v[0] if len(v) == 1 else v + for k, v in parse_qs(environ.get("QUERY_STRING", "")).items() + }, + "secure": environ.get("wsgi.url_scheme", "") == "https", + "url": environ.get("PATH_INFO", ""), + "issued": tm * 1000, + "time": datetime.fromtimestamp(tm, timezone.utc).isoformat() + if tm + else "", }, - 'rooms': self.sio.manager.get_rooms(sid, namespace), + "rooms": self.sio.manager.get_rooms(sid, namespace), } diff --git a/src/socketio/asgi.py b/src/socketio/asgi.py index 23b094d..7fcdfa2 100644 --- a/src/socketio/asgi.py +++ b/src/socketio/asgi.py @@ -32,16 +32,30 @@ class ASGIApp(engineio.ASGIApp): # pragma: no cover import uvicorn sio = socketio.AsyncServer() - app = socketio.ASGIApp(sio, static_files={ - '/': 'index.html', - '/static': './public', - }) - uvicorn.run(app, host='127.0.0.1', port=5000) + app = socketio.ASGIApp( + sio, + static_files={ + "/": "index.html", + "/static": "./public", + }, + ) + uvicorn.run(app, host="127.0.0.1", port=5000) """ - def __init__(self, socketio_server, other_asgi_app=None, - static_files=None, socketio_path='socket.io', - on_startup=None, on_shutdown=None): - super().__init__(socketio_server, other_asgi_app, - static_files=static_files, - engineio_path=socketio_path, on_startup=on_startup, - on_shutdown=on_shutdown) + + def __init__( + self, + socketio_server, + other_asgi_app=None, + static_files=None, + socketio_path="socket.io", + on_startup=None, + on_shutdown=None, + ): + super().__init__( + socketio_server, + other_asgi_app, + static_files=static_files, + engineio_path=socketio_path, + on_startup=on_startup, + on_shutdown=on_shutdown, + ) diff --git a/src/socketio/async_admin.py b/src/socketio/async_admin.py index b052d8f..510f926 100644 --- a/src/socketio/async_admin.py +++ b/src/socketio/async_admin.py @@ -1,10 +1,11 @@ import asyncio -from datetime import datetime, timezone import functools import os import socket import time +from datetime import datetime, timezone from urllib.parse import parse_qs + from .admin import EventBuffer from .exceptions import ConnectionRefusedError @@ -13,19 +14,28 @@ PID = os.getpid() class InstrumentedAsyncServer: - def __init__(self, sio, auth=None, namespace='/admin', read_only=False, - server_id=None, mode='development', server_stats_interval=2): + def __init__( + self, + sio, + auth=None, + namespace="/admin", + read_only=False, + server_id=None, + mode="development", + server_stats_interval=2, + ): """Instrument the Socket.IO server for monitoring with the `Socket.IO Admin UI `_. """ if auth is None: - raise ValueError('auth must be specified') + raise ValueError("auth must be specified") self.sio = sio self.auth = auth self.admin_namespace = namespace self.read_only = read_only self.server_id = server_id or ( - self.sio.manager.host_id if hasattr(self.sio.manager, 'host_id') + self.sio.manager.host_id + if hasattr(self.sio.manager, "host_id") else HOSTNAME ) self.mode = mode @@ -41,19 +51,20 @@ class InstrumentedAsyncServer: self.instrument() def instrument(self): - self.sio.on('connect', self.admin_connect, - namespace=self.admin_namespace) + self.sio.on("connect", self.admin_connect, namespace=self.admin_namespace) - if self.mode == 'development': + if self.mode == "development": if not self.read_only: # pragma: no branch - self.sio.on('emit', self.admin_emit, - namespace=self.admin_namespace) - self.sio.on('join', self.admin_enter_room, - namespace=self.admin_namespace) - self.sio.on('leave', self.admin_leave_room, - namespace=self.admin_namespace) - self.sio.on('_disconnect', self.admin_disconnect, - namespace=self.admin_namespace) + self.sio.on("emit", self.admin_emit, namespace=self.admin_namespace) + self.sio.on( + "join", self.admin_enter_room, namespace=self.admin_namespace + ) + self.sio.on( + "leave", self.admin_leave_room, namespace=self.admin_namespace + ) + self.sio.on( + "_disconnect", self.admin_disconnect, namespace=self.admin_namespace + ) # track socket connection times self.sio.manager._timestamps = {} @@ -63,13 +74,11 @@ class InstrumentedAsyncServer: self.sio._trigger_event = self._trigger_event # report join rooms - self.sio.manager.__basic_enter_room = \ - self.sio.manager.basic_enter_room + self.sio.manager.__basic_enter_room = self.sio.manager.basic_enter_room self.sio.manager.basic_enter_room = self._basic_enter_room # report leave rooms - self.sio.manager.__basic_leave_room = \ - self.sio.manager.basic_leave_room + self.sio.manager.__basic_leave_room = self.sio.manager.basic_leave_room self.sio.manager.basic_leave_room = self._basic_leave_room # report emit events @@ -77,42 +86,45 @@ class InstrumentedAsyncServer: self.sio.manager.emit = self._emit # report engine.io connections - self.sio.eio.on('connect', self._handle_eio_connect) - self.sio.eio.on('disconnect', self._handle_eio_disconnect) + self.sio.eio.on("connect", self._handle_eio_connect) + self.sio.eio.on("disconnect", self._handle_eio_disconnect) # report polling packets from engineio.async_socket import AsyncSocket + self.sio.eio.__ok = self.sio.eio._ok self.sio.eio._ok = self._eio_http_response AsyncSocket.__handle_post_request = AsyncSocket.handle_post_request AsyncSocket.handle_post_request = functools.partialmethod( - self.__class__._eio_handle_post_request, self) + self.__class__._eio_handle_post_request, self + ) # report websocket packets AsyncSocket.__websocket_handler = AsyncSocket._websocket_handler AsyncSocket._websocket_handler = functools.partialmethod( - self.__class__._eio_websocket_handler, self) + self.__class__._eio_websocket_handler, self + ) # report connected sockets with each ping - if self.mode == 'development': + if self.mode == "development": AsyncSocket.__send_ping = AsyncSocket._send_ping AsyncSocket._send_ping = functools.partialmethod( - self.__class__._eio_send_ping, self) + self.__class__._eio_send_ping, self + ) def uninstrument(self): # pragma: no cover - if self.mode == 'development': + if self.mode == "development": 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.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.eio._ok = self.sio.eio.__ok from engineio.async_socket import AsyncSocket + AsyncSocket.handle_post_request = AsyncSocket.__handle_post_request AsyncSocket._websocket_handler = AsyncSocket.__websocket_handler - if self.mode == 'development': + if self.mode == "development": AsyncSocket._send_ping = AsyncSocket.__send_ping async def admin_connect(self, sid, environ, client_auth): @@ -129,53 +141,59 @@ class InstrumentedAsyncServer: else: authenticated = self.auth(client_auth) if not authenticated: - raise ConnectionRefusedError('authentication failed') + raise ConnectionRefusedError("authentication failed") async def config(sid): await self.sio.sleep(0.1) # supported features - features = ['AGGREGATED_EVENTS'] + features = ["AGGREGATED_EVENTS"] if not self.read_only: - features += ['EMIT', 'JOIN', 'LEAVE', 'DISCONNECT', 'MJOIN', - 'MLEAVE', 'MDISCONNECT'] - if self.mode == 'development': - features.append('ALL_EVENTS') - await self.sio.emit('config', {'supportedFeatures': features}, - to=sid, namespace=self.admin_namespace) + features += [ + "EMIT", + "JOIN", + "LEAVE", + "DISCONNECT", + "MJOIN", + "MLEAVE", + "MDISCONNECT", + ] + if self.mode == "development": + features.append("ALL_EVENTS") + await self.sio.emit( + "config", + {"supportedFeatures": features}, + to=sid, + namespace=self.admin_namespace, + ) # send current sockets - if self.mode == 'development': + if self.mode == "development": all_sockets = [] for nsp in self.sio.manager.get_namespaces(): - for sid, eio_sid in self.sio.manager.get_participants( - nsp, None): - all_sockets.append( - self.serialize_socket(sid, nsp, eio_sid)) - await self.sio.emit('all_sockets', all_sockets, to=sid, - namespace=self.admin_namespace) + for sid, eio_sid in self.sio.manager.get_participants(nsp, None): + all_sockets.append(self.serialize_socket(sid, nsp, eio_sid)) + await self.sio.emit( + "all_sockets", all_sockets, to=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) + 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) async def admin_enter_room(self, _, namespace, room, room_filter=None): - for sid, _ in self.sio.manager.get_participants( - namespace, room_filter): + for sid, _ in self.sio.manager.get_participants(namespace, room_filter): await self.sio.enter_room(sid, room, namespace=namespace) async def admin_leave_room(self, _, namespace, room, room_filter=None): - for sid, _ in self.sio.manager.get_participants( - namespace, room_filter): + for sid, _ in self.sio.manager.get_participants(namespace, room_filter): await self.sio.leave_room(sid, room, namespace=namespace) async def admin_disconnect(self, _, namespace, close, room_filter=None): - for sid, _ in self.sio.manager.get_participants( - namespace, room_filter): + for sid, _ in self.sio.manager.get_participants(namespace, room_filter): await self.sio.disconnect(sid, namespace=namespace) async def shutdown(self): @@ -186,126 +204,161 @@ class InstrumentedAsyncServer: async def _trigger_event(self, event, namespace, *args): t = time.time() sid = args[0] - if event == 'connect': + 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.fromtimestamp(t, timezone.utc).isoformat(), - ), namespace=self.admin_namespace) - elif event == 'disconnect': + await self.sio.emit( + "socket_connected", + ( + serialized_socket, + datetime.fromtimestamp(t, timezone.utc).isoformat(), + ), + 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.fromtimestamp(t, timezone.utc).isoformat(), - ), namespace=self.admin_namespace) + await self.sio.emit( + "socket_disconnected", + ( + namespace, + sid, + reason, + datetime.fromtimestamp(t, timezone.utc).isoformat(), + ), + namespace=self.admin_namespace, + ) else: - await self.sio.emit('event_received', ( - namespace, - sid, - (event, *args[1:]), - datetime.fromtimestamp(t, timezone.utc).isoformat(), - ), namespace=self.admin_namespace) + await self.sio.emit( + "event_received", + ( + namespace, + sid, + (event, *args[1:]), + datetime.fromtimestamp(t, timezone.utc).isoformat(), + ), + 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 + async def _check_for_upgrade(self, eio_sid, sid, namespace): # pragma: no cover for _ in range(5): await self.sio.sleep(5) try: if self.sio.eio._get_socket(eio_sid).upgraded: - await self.sio.emit('socket_updated', { - 'id': sid, - 'nsp': namespace, - 'transport': 'websocket', - }, namespace=self.admin_namespace) + await self.sio.emit( + "socket_updated", + { + "id": sid, + "nsp": namespace, + "transport": "websocket", + }, + namespace=self.admin_namespace, + ) break except KeyError: pass def _basic_enter_room(self, sid, namespace, room, eio_sid=None): - ret = self.sio.manager.__basic_enter_room(sid, namespace, room, - eio_sid) + ret = self.sio.manager.__basic_enter_room(sid, namespace, room, eio_sid) if room: - self.admin_queue.append(('room_joined', ( - namespace, - room, - sid, - datetime.now(timezone.utc).isoformat(), - ))) + self.admin_queue.append( + ( + "room_joined", + ( + namespace, + room, + sid, + datetime.now(timezone.utc).isoformat(), + ), + ) + ) return ret def _basic_leave_room(self, sid, namespace, room): if room: - self.admin_queue.append(('room_left', ( - namespace, - room, - sid, - datetime.now(timezone.utc).isoformat(), - ))) + self.admin_queue.append( + ( + "room_left", + ( + namespace, + room, + sid, + datetime.now(timezone.utc).isoformat(), + ), + ) + ) return self.sio.manager.__basic_leave_room(sid, namespace, room) - async def _emit(self, event, data, namespace, room=None, skip_sid=None, - callback=None, **kwargs): + async def _emit( + self, event, data, namespace, room=None, skip_sid=None, callback=None, **kwargs + ): ret = await self.sio.manager.__emit( - event, data, namespace, room=room, skip_sid=skip_sid, - callback=callback, **kwargs) + event, + data, + namespace, + room=room, + skip_sid=skip_sid, + callback=callback, + **kwargs, + ) if namespace != self.admin_namespace: - event_data = [event] + list(data) if isinstance(data, tuple) \ - else [event, data] + event_data = ( + [event] + list(data) if isinstance(data, tuple) 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): if sid not in skip_sid: - await self.sio.emit('event_sent', ( - namespace, - sid, - event_data, - datetime.now(timezone.utc).isoformat(), - ), namespace=self.admin_namespace) + await self.sio.emit( + "event_sent", + ( + namespace, + sid, + event_data, + datetime.now(timezone.utc).isoformat(), + ), + 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() - self.stats_task = self.sio.start_background_task( - self._emit_server_stats) + self.stats_task = self.sio.start_background_task(self._emit_server_stats) - self.event_buffer.push('rawConnection') + self.event_buffer.push("rawConnection") return await self.sio._handle_eio_connect(eio_sid, environ) async def _handle_eio_disconnect(self, eio_sid, reason): - self.event_buffer.push('rawDisconnection') + self.event_buffer.push("rawDisconnection") 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, - jsonp_index=jsonp_index) - self.event_buffer.push('packetsOut') - self.event_buffer.push('bytesOut', len(ret['response'])) + ret = self.sio.eio.__ok( + packets=packets, headers=headers, jsonp_index=jsonp_index + ) + self.event_buffer.push("packetsOut") + self.event_buffer.push("bytesOut", len(ret["response"])) return ret async def _eio_handle_post_request(socket, self, environ): ret = await socket.__handle_post_request(environ) - self.event_buffer.push('packetsIn') - self.event_buffer.push( - 'bytesIn', int(environ.get('CONTENT_LENGTH', 0))) + self.event_buffer.push("packetsIn") + self.event_buffer.push("bytesIn", int(environ.get("CONTENT_LENGTH", 0))) return ret async def _eio_websocket_handler(socket, self, ws): async def _send(ws, data): - self.event_buffer.push('packetsOut') - self.event_buffer.push('bytesOut', len(data)) + self.event_buffer.push("packetsOut") + self.event_buffer.push("bytesOut", len(data)) return await ws.__send(data) async def _wait(ws): ret = await ws.__wait() - self.event_buffer.push('packetsIn') - self.event_buffer.push('bytesIn', len(ret or '')) + self.event_buffer.push("packetsIn") + self.event_buffer.push("bytesIn", len(ret or "")) return ret ws.__send = ws.send @@ -320,12 +373,15 @@ class InstrumentedAsyncServer: for namespace in self.sio.manager.get_namespaces(): sid = self.sio.manager.sid_from_eio_sid(eio_sid, namespace) if sid: - serialized_socket = self.serialize_socket(sid, namespace, - eio_sid) - await self.sio.emit('socket_connected', ( - serialized_socket, - datetime.fromtimestamp(t, timezone.utc).isoformat(), - ), namespace=self.admin_namespace) + serialized_socket = self.serialize_socket(sid, namespace, eio_sid) + await self.sio.emit( + "socket_connected", + ( + serialized_socket, + datetime.fromtimestamp(t, timezone.utc).isoformat(), + ), + namespace=self.admin_namespace, + ) return await socket.__send_ping() async def _emit_server_stats(self): @@ -334,51 +390,69 @@ class InstrumentedAsyncServer: namespaces.sort() while not self.stop_stats_event.is_set(): await self.sio.sleep(self.server_stats_interval) - await self.sio.emit('server_stats', { - 'serverId': self.server_id, - 'hostname': HOSTNAME, - 'pid': PID, - 'uptime': time.time() - start_time, - 'clientsCount': len(self.sio.eio.sockets), - 'pollingClientsCount': len( - [s for s in self.sio.eio.sockets.values() - if not s.upgraded]), - 'aggregatedEvents': self.event_buffer.get_and_clear(), - 'namespaces': [{ - 'name': nsp, - 'socketsCount': len(self.sio.manager.rooms.get( - nsp, {None: []}).get(None, [])) - } for nsp in namespaces], - }, namespace=self.admin_namespace) + await self.sio.emit( + "server_stats", + { + "serverId": self.server_id, + "hostname": HOSTNAME, + "pid": PID, + "uptime": time.time() - start_time, + "clientsCount": len(self.sio.eio.sockets), + "pollingClientsCount": len( + [s for s in self.sio.eio.sockets.values() if not s.upgraded] + ), + "aggregatedEvents": self.event_buffer.get_and_clear(), + "namespaces": [ + { + "name": nsp, + "socketsCount": len( + self.sio.manager.rooms.get(nsp, {None: []}).get( + None, [] + ) + ), + } + for nsp in namespaces + ], + }, + namespace=self.admin_namespace, + ) while self.admin_queue: event, args = self.admin_queue.pop(0) - await self.sio.emit(event, args, - namespace=self.admin_namespace) + await self.sio.emit(event, args, namespace=self.admin_namespace) def serialize_socket(self, sid, namespace, eio_sid=None): if eio_sid is None: # pragma: no cover eio_sid = self.sio.manager.eio_sid_from_sid(sid) socket = self.sio.eio._get_socket(eio_sid) environ = self.sio.environ.get(eio_sid, {}) - tm = self.sio.manager._timestamps[sid] if sid in \ - self.sio.manager._timestamps else 0 + tm = ( + self.sio.manager._timestamps[sid] + if sid in self.sio.manager._timestamps + else 0 + ) return { - 'id': sid, - 'clientId': eio_sid, - 'transport': 'websocket' if socket.upgraded else 'polling', - 'nsp': namespace, - 'data': {}, - 'handshake': { - 'address': environ.get('REMOTE_ADDR', ''), - 'headers': {k[5:].lower(): v for k, v in environ.items() - if k.startswith('HTTP_')}, - 'query': {k: v[0] if len(v) == 1 else v for k, v in parse_qs( - environ.get('QUERY_STRING', '')).items()}, - 'secure': environ.get('wsgi.url_scheme', '') == 'https', - 'url': environ.get('PATH_INFO', ''), - 'issued': tm * 1000, - 'time': datetime.fromtimestamp(tm, timezone.utc).isoformat() - if tm else '', + "id": sid, + "clientId": eio_sid, + "transport": "websocket" if socket.upgraded else "polling", + "nsp": namespace, + "data": {}, + "handshake": { + "address": environ.get("REMOTE_ADDR", ""), + "headers": { + k[5:].lower(): v + for k, v in environ.items() + if k.startswith("HTTP_") + }, + "query": { + k: v[0] if len(v) == 1 else v + for k, v in parse_qs(environ.get("QUERY_STRING", "")).items() + }, + "secure": environ.get("wsgi.url_scheme", "") == "https", + "url": environ.get("PATH_INFO", ""), + "issued": tm * 1000, + "time": datetime.fromtimestamp(tm, timezone.utc).isoformat() + if tm + else "", }, - 'rooms': self.sio.manager.get_rooms(sid, namespace), + "rooms": self.sio.manager.get_rooms(sid, namespace), } diff --git a/src/socketio/async_aiopika_manager.py b/src/socketio/async_aiopika_manager.py index 003b67b..b2fdede 100644 --- a/src/socketio/async_aiopika_manager.py +++ b/src/socketio/async_aiopika_manager.py @@ -19,9 +19,8 @@ class AsyncAioPikaManager(AsyncPubSubManager): # pragma: no cover To use a aio_pika backend, initialize the :class:`Server` instance as follows:: - url = 'amqp://user:password@hostname:port//' - server = socketio.Server(client_manager=socketio.AsyncAioPikaManager( - url)) + url = "amqp://user:password@hostname:port//" + server = socketio.Server(client_manager=socketio.AsyncAioPikaManager(url)) :param url: The connection URL for the backend messaging queue. Example connection URLs are ``'amqp://guest:guest@localhost:5672//'`` @@ -35,14 +34,21 @@ class AsyncAioPikaManager(AsyncPubSubManager): # pragma: no cover and receiving. """ - name = 'asyncaiopika' + name = "asyncaiopika" - def __init__(self, url='amqp://guest:guest@localhost:5672//', - channel='socketio', write_only=False, logger=None): + def __init__( + self, + url="amqp://guest:guest@localhost:5672//", + channel="socketio", + write_only=False, + logger=None, + ): if aio_pika is None: - raise RuntimeError('aio_pika package is not installed ' - '(Run "pip install aio_pika" in your ' - 'virtualenv).') + 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() @@ -57,12 +63,14 @@ class AsyncAioPikaManager(AsyncPubSubManager): # pragma: no cover return await connection.channel() async def _exchange(self, channel): - return await channel.declare_exchange(self.channel, - aio_pika.ExchangeType.FANOUT) + return await channel.declare_exchange( + self.channel, aio_pika.ExchangeType.FANOUT + ) async def _queue(self, channel, exchange): - queue = await channel.declare_queue(durable=False, - arguments={'x-expires': 300000}) + queue = await channel.declare_queue( + durable=False, arguments={"x-expires": 300000} + ) await queue.bind(exchange) return queue @@ -83,25 +91,26 @@ class AsyncAioPikaManager(AsyncPubSubManager): # pragma: no cover await self.publisher_exchange.publish( aio_pika.Message( body=pickle.dumps(data), - delivery_mode=aio_pika.DeliveryMode.PERSISTENT - ), routing_key='*', + delivery_mode=aio_pika.DeliveryMode.PERSISTENT, + ), + routing_key="*", ) break except aio_pika.AMQPException: if retry: - self._get_logger().error('Cannot publish to rabbitmq... ' - 'retrying') + self._get_logger().error( + "Cannot publish to rabbitmq... " "retrying" + ) retry = False else: - self._get_logger().error( - 'Cannot publish to rabbitmq... giving up') + self._get_logger().error("Cannot publish to rabbitmq... giving up") break except aio_pika.exceptions.ChannelInvalidStateError: # aio_pika raises this exception when the task is cancelled raise asyncio.CancelledError() async def _listen(self): - async with (await self._connection()) as connection: + async with await self._connection() as connection: channel = await self._channel(connection) await channel.set_qos(prefetch_count=1) exchange = await self._exchange(channel) @@ -117,8 +126,9 @@ class AsyncAioPikaManager(AsyncPubSubManager): # pragma: no cover retry_sleep = 1 except aio_pika.AMQPException: self._get_logger().error( - 'Cannot receive from rabbitmq... ' - 'retrying in {} secs'.format(retry_sleep)) + "Cannot receive from rabbitmq... " + f"retrying in {retry_sleep} secs" + ) await asyncio.sleep(retry_sleep) retry_sleep = min(retry_sleep * 2, 60) except aio_pika.exceptions.ChannelInvalidStateError: diff --git a/src/socketio/async_client.py b/src/socketio/async_client.py index 0a0137c..9afc466 100644 --- a/src/socketio/async_client.py +++ b/src/socketio/async_client.py @@ -4,11 +4,9 @@ import random import engineio -from . import base_client -from . import exceptions -from . import packet +from . import base_client, exceptions, packet -default_logger = logging.getLogger('socketio.client') +default_logger = logging.getLogger("socketio.client") class AsyncClient(base_client.BaseClient): @@ -67,12 +65,22 @@ class AsyncClient(base_client.BaseClient): fatal errors are logged even when ``engineio_logger`` is ``False``. """ + def is_asyncio_based(self): return True - async def connect(self, url, headers={}, auth=None, transports=None, - namespaces=None, socketio_path='socket.io', wait=True, - wait_timeout=1, retry=False): + async def connect( + self, + url, + headers={}, + auth=None, + transports=None, + namespaces=None, + socketio_path="socket.io", + wait=True, + wait_timeout=1, + retry=False, + ): """Connect to a Socket.IO server. :param url: The URL of the Socket.IO server. It can include custom @@ -116,10 +124,10 @@ class AsyncClient(base_client.BaseClient): Example usage:: sio = socketio.AsyncClient() - await sio.connect('http://localhost:5000') + await sio.connect("http://localhost:5000") """ if self.connected: - raise exceptions.ConnectionError('Already connected') + raise exceptions.ConnectionError("Already connected") self.connection_url = url self.connection_headers = headers @@ -129,12 +137,13 @@ class AsyncClient(base_client.BaseClient): self.socketio_path = socketio_path if namespaces is None: - namespaces = list(set(self.handlers.keys()).union( - set(self.namespace_handlers.keys()))) - if '*' in namespaces: - namespaces.remove('*') + namespaces = list( + set(self.handlers.keys()).union(set(self.namespace_handlers.keys())) + ) + if "*" in namespaces: + namespaces.remove("*") if len(namespaces) == 0: - namespaces = ['/'] + namespaces = ["/"] elif isinstance(namespaces, str): namespaces = [namespaces] self.connection_namespaces = namespaces @@ -146,25 +155,29 @@ class AsyncClient(base_client.BaseClient): real_url = await self._get_real_value(self.connection_url) real_headers = await self._get_real_value(self.connection_headers) try: - await self.eio.connect(real_url, headers=real_headers, - transports=transports, - engineio_path=socketio_path) + await self.eio.connect( + real_url, + headers=real_headers, + transports=transports, + engineio_path=socketio_path, + ) except engineio.exceptions.ConnectionError as exc: for n in self.connection_namespaces: await self._trigger_event( - 'connect_error', n, - exc.args[1] if len(exc.args) > 1 else exc.args[0]) + "connect_error", + n, + exc.args[1] if len(exc.args) > 1 else exc.args[0], + ) if retry: # pragma: no cover await self._handle_reconnect() - if self.eio.state == 'connected': + if self.eio.state == "connected": return raise exceptions.ConnectionError(exc.args[0]) from exc if wait: try: while True: - await asyncio.wait_for(self._connect_event.wait(), - wait_timeout) + await asyncio.wait_for(self._connect_event.wait(), wait_timeout) self._connect_event.clear() if set(self.namespaces) == set(self.connection_namespaces): break @@ -173,7 +186,8 @@ class AsyncClient(base_client.BaseClient): 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" + ) self.connected = True @@ -189,12 +203,12 @@ class AsyncClient(base_client.BaseClient): 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 + if self.eio.state == "connected": # pragma: no cover # connected while sleeping above continue break await self._reconnect_task - if self.eio.state != 'connected': + if self.eio.state != "connected": break async def emit(self, event, data=None, namespace=None, callback=None): @@ -223,10 +237,11 @@ class AsyncClient(base_client.BaseClient): Note 2: this method is a coroutine. """ - namespace = namespace or '/' + namespace = namespace or "/" if namespace not in self.namespaces: raise exceptions.BadNamespaceError( - namespace + ' is not a connected namespace.') + namespace + " is not a connected namespace." + ) self.logger.info('Emitting event "%s" [%s]', event, namespace) if callback is not None: id = self._generate_ack_id(namespace, callback) @@ -240,8 +255,11 @@ class AsyncClient(base_client.BaseClient): data = [data] else: data = [] - await self._send_packet(self.packet_class( - packet.EVENT, namespace=namespace, data=[event] + data, id=id)) + await self._send_packet( + self.packet_class( + packet.EVENT, namespace=namespace, data=[event] + data, id=id + ) + ) async def send(self, data, namespace=None, callback=None): """Send a message to the server. @@ -263,8 +281,7 @@ class AsyncClient(base_client.BaseClient): Note: this method is a coroutine. """ - await self.emit('message', data=data, namespace=namespace, - callback=callback) + await self.emit("message", data=data, namespace=namespace, callback=callback) async def call(self, event, data=None, namespace=None, timeout=60): """Emit a custom event to the server and wait for the response. @@ -304,15 +321,18 @@ class AsyncClient(base_client.BaseClient): callback_args.append(args) callback_event.set() - await self.emit(event, data=data, namespace=namespace, - callback=event_callback) + await self.emit(event, data=data, namespace=namespace, callback=event_callback) try: await asyncio.wait_for(callback_event.wait(), timeout) except asyncio.TimeoutError: raise exceptions.TimeoutError() from None - return callback_args[0] if len(callback_args[0]) > 1 \ - else callback_args[0][0] if len(callback_args[0]) == 1 \ + return ( + callback_args[0] + if len(callback_args[0]) > 1 + else callback_args[0][0] + if len(callback_args[0]) == 1 else None + ) async def disconnect(self): """Disconnect from the server. @@ -322,8 +342,7 @@ class AsyncClient(base_client.BaseClient): # here we just request the disconnection # later in _handle_eio_disconnect we invoke the disconnect handler for n in self.namespaces: - await self._send_packet(self.packet_class(packet.DISCONNECT, - namespace=n)) + await self._send_packet(self.packet_class(packet.DISCONNECT, namespace=n)) await self.eio.disconnect() async def shutdown(self): @@ -369,7 +388,8 @@ class AsyncClient(base_client.BaseClient): async def _get_real_value(self, value): """Return the actual value, for parameters that can also be given as - callables.""" + callables. + """ if not callable(value): return value if asyncio.iscoroutinefunction(value): @@ -386,20 +406,21 @@ class AsyncClient(base_client.BaseClient): await self.eio.send(encoded_packet) async def _handle_connect(self, namespace, data): - namespace = namespace or '/' + namespace = namespace or "/" if namespace not in self.namespaces: - 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.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() async def _handle_disconnect(self, namespace): if not self.connected: return - namespace = namespace or '/' - await self._trigger_event('disconnect', namespace, - self.reason.SERVER_DISCONNECT) - await self._trigger_event('__disconnect_final', namespace) + namespace = namespace or "/" + 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: @@ -407,7 +428,7 @@ class AsyncClient(base_client.BaseClient): await self.eio.disconnect(abort=True) async def _handle_event(self, namespace, id, data): - namespace = namespace or '/' + namespace = namespace or "/" self.logger.info('Received event "%s" [%s]', data[0], namespace) r = await self._trigger_event(data[0], namespace, *data[1:]) if id is not None: @@ -419,18 +440,19 @@ class AsyncClient(base_client.BaseClient): data = list(r) else: data = [r] - await self._send_packet(self.packet_class( - packet.ACK, namespace=namespace, id=id, data=data)) + await self._send_packet( + self.packet_class(packet.ACK, namespace=namespace, id=id, data=data) + ) async def _handle_ack(self, namespace, id, data): - namespace = namespace or '/' - self.logger.info('Received ack [%s]', namespace) + namespace = namespace or "/" + self.logger.info("Received ack [%s]", namespace) callback = None try: callback = self.callbacks[namespace][id] except KeyError: # if we get an unknown callback we just ignore it - self.logger.warning('Unknown callback received, ignoring.') + self.logger.warning("Unknown callback received, ignoring.") else: del self.callbacks[namespace][id] if callback is not None: @@ -440,18 +462,17 @@ class AsyncClient(base_client.BaseClient): callback(*data) async def _handle_error(self, namespace, data): - namespace = namespace or '/' - self.logger.info('Connection to namespace {} was rejected'.format( - namespace)) + namespace = namespace or "/" + self.logger.info(f"Connection to namespace {namespace} was rejected") if data is None: data = tuple() elif not isinstance(data, (tuple, list)): data = (data,) - await self._trigger_event('connect_error', namespace, *data) + await self._trigger_event("connect_error", namespace, *data) self._connect_event.set() if namespace in self.namespaces: del self.namespaces[namespace] - if namespace == '/': + if namespace == "/": self.namespaces = {} self.connected = False @@ -467,7 +488,7 @@ class AsyncClient(base_client.BaseClient): except TypeError: # the legacy disconnect event does not take a reason # argument - if event == 'disconnect': + if event == "disconnect": ret = await handler(*args[:-1]) else: # pragma: no cover raise @@ -479,7 +500,7 @@ class AsyncClient(base_client.BaseClient): except TypeError: # the legacy disconnect event does not take a reason # argument - if event == 'disconnect': + if event == "disconnect": ret = handler(*args[:-1]) else: # pragma: no cover raise @@ -503,9 +524,7 @@ class AsyncClient(base_client.BaseClient): if delay > self.reconnection_delay_max: delay = self.reconnection_delay_max delay += self.randomization_factor * (2 * random.random() - 1) - self.logger.info( - 'Connection failed, new attempt in {:.02f} seconds'.format( - delay)) + self.logger.info(f"Connection failed, new attempt in {delay:.02f} seconds") abort = False try: await asyncio.wait_for(self._reconnect_abort.wait(), delay) @@ -515,44 +534,46 @@ class AsyncClient(base_client.BaseClient): except asyncio.CancelledError: # pragma: no cover abort = True if abort: - self.logger.info('Reconnect task aborted') + self.logger.info("Reconnect task aborted") for n in self.connection_namespaces: - await self._trigger_event('__disconnect_final', - namespace=n) + await self._trigger_event("__disconnect_final", namespace=n) break attempt_count += 1 try: - await self.connect(self.connection_url, - headers=self.connection_headers, - auth=self.connection_auth, - transports=self.connection_transports, - namespaces=self.connection_namespaces, - socketio_path=self.socketio_path, - retry=False) + await self.connect( + self.connection_url, + headers=self.connection_headers, + auth=self.connection_auth, + transports=self.connection_transports, + namespaces=self.connection_namespaces, + socketio_path=self.socketio_path, + retry=False, + ) except (exceptions.ConnectionError, ValueError): pass else: - self.logger.info('Reconnection successful') + self.logger.info("Reconnection successful") self._reconnect_task = None break - if self.reconnection_attempts and \ - attempt_count >= self.reconnection_attempts: - self.logger.info( - 'Maximum reconnection attempts reached, giving up') + if ( + self.reconnection_attempts + and attempt_count >= self.reconnection_attempts + ): + self.logger.info("Maximum reconnection attempts reached, giving up") for n in self.connection_namespaces: - await self._trigger_event('__disconnect_final', - namespace=n) + await self._trigger_event("__disconnect_final", namespace=n) break base_client.reconnecting_clients.remove(self) async def _handle_eio_connect(self): """Handle the Engine.IO connection event.""" - self.logger.info('Engine.IO connection established') + self.logger.info("Engine.IO connection established") self.sid = self.eio.sid real_auth = await self._get_real_value(self.connection_auth) or {} for n in self.connection_namespaces: - await self._send_packet(self.packet_class( - packet.CONNECT, data=real_auth, namespace=n)) + await self._send_packet( + self.packet_class(packet.CONNECT, data=real_auth, namespace=n) + ) async def _handle_eio_message(self, data): """Dispatch Engine.IO messages.""" @@ -574,31 +595,32 @@ class AsyncClient(base_client.BaseClient): await self._handle_event(pkt.namespace, pkt.id, pkt.data) elif pkt.packet_type == packet.ACK: await self._handle_ack(pkt.namespace, pkt.id, pkt.data) - elif pkt.packet_type == packet.BINARY_EVENT or \ - pkt.packet_type == packet.BINARY_ACK: + elif ( + pkt.packet_type == packet.BINARY_EVENT + or pkt.packet_type == packet.BINARY_ACK + ): self._binary_packet = pkt elif pkt.packet_type == packet.CONNECT_ERROR: await self._handle_error(pkt.namespace, pkt.data) else: - raise ValueError('Unknown packet type.') + raise ValueError("Unknown packet type.") 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' + 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', n, reason) + await self._trigger_event("disconnect", n, reason) if not will_reconnect: - await self._trigger_event('__disconnect_final', n) + await self._trigger_event("__disconnect_final", n) self.namespaces = {} self.connected = False self.callbacks = {} self._binary_packet = None self.sid = None if will_reconnect and not self._reconnect_task: - self._reconnect_task = self.start_background_task( - self._handle_reconnect) + self._reconnect_task = self.start_background_task(self._handle_reconnect) def _engineio_client_class(self): return engineio.AsyncClient diff --git a/src/socketio/async_manager.py b/src/socketio/async_manager.py index 47e7a79..8877785 100644 --- a/src/socketio/async_manager.py +++ b/src/socketio/async_manager.py @@ -1,17 +1,29 @@ import asyncio from engineio import packet as eio_packet + from socketio import packet + from .base_manager import BaseManager class AsyncManager(BaseManager): """Manage a client list for an asyncio server.""" + async def can_disconnect(self, sid, namespace): return self.is_connected(sid, namespace) - async def emit(self, event, data, namespace, room=None, skip_sid=None, - callback=None, to=None, **kwargs): + async def emit( + self, + event, + data, + namespace, + room=None, + skip_sid=None, + callback=None, + to=None, + **kwargs, + ): """Emit a message to a single client, a room, or all the clients connected to the namespace. @@ -35,17 +47,20 @@ class AsyncManager(BaseManager): # when callbacks aren't used the packets sent to each recipient are # identical, so they can be generated once and reused pkt = self.server.packet_class( - packet.EVENT, namespace=namespace, data=[event] + data) + packet.EVENT, namespace=namespace, data=[event] + data + ) encoded_packet = pkt.encode() if not isinstance(encoded_packet, list): encoded_packet = [encoded_packet] - eio_pkt = [eio_packet.Packet(eio_packet.MESSAGE, p) - for p in encoded_packet] + eio_pkt = [eio_packet.Packet(eio_packet.MESSAGE, p) for p in encoded_packet] for sid, eio_sid in self.get_participants(namespace, room): if sid not in skip_sid: for p in eio_pkt: - tasks.append(asyncio.create_task( - self.server._send_eio_packet(eio_sid, p))) + tasks.append( + asyncio.create_task( + self.server._send_eio_packet(eio_sid, p) + ) + ) else: # callbacks are used, so each recipient must be sent a packet that # contains a unique callback id @@ -55,10 +70,11 @@ class AsyncManager(BaseManager): if sid not in skip_sid: # pragma: no branch id = self._generate_ack_id(sid, callback) pkt = self.server.packet_class( - packet.EVENT, namespace=namespace, data=[event] + data, - id=id) - tasks.append(asyncio.create_task( - self.server._send_packet(eio_sid, pkt))) + packet.EVENT, namespace=namespace, data=[event] + data, id=id + ) + tasks.append( + asyncio.create_task(self.server._send_packet(eio_sid, pkt)) + ) if tasks == []: # pragma: no cover return await asyncio.wait(tasks) @@ -108,7 +124,7 @@ class AsyncManager(BaseManager): callback = self.callbacks[sid][id] except KeyError: # if we get an unknown callback we just ignore it - self._get_logger().warning('Unknown callback received, ignoring.') + self._get_logger().warning("Unknown callback received, ignoring.") else: del self.callbacks[sid][id] if callback is not None: diff --git a/src/socketio/async_namespace.py b/src/socketio/async_namespace.py index 42d6508..ecfd64d 100644 --- a/src/socketio/async_namespace.py +++ b/src/socketio/async_namespace.py @@ -16,6 +16,7 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): handlers defined in this class. If this argument is omitted, the default namespace is used. """ + def is_asyncio_based(self): return True @@ -29,7 +30,7 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): Note: this method is a coroutine. """ - handler_name = 'on_' + (event or '') + handler_name = "on_" + (event or "") if hasattr(self, handler_name): handler = getattr(self, handler_name) if asyncio.iscoroutinefunction(handler) is True: @@ -39,7 +40,7 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): except TypeError: # legacy disconnect events do not have a reason # argument - if event == 'disconnect': + if event == "disconnect": ret = await handler(*args[:-1]) else: # pragma: no cover raise @@ -51,14 +52,23 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): except TypeError: # legacy disconnect events do not have a reason # argument - if event == 'disconnect': + 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, - namespace=None, callback=None, ignore_queue=False): + async def emit( + self, + event, + data=None, + to=None, + room=None, + skip_sid=None, + namespace=None, + callback=None, + ignore_queue=False, + ): """Emit a custom event to one or more connected clients. The only difference with the :func:`socketio.Server.emit` method is @@ -67,14 +77,27 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): Note: this method is a coroutine. """ - return await self.server.emit(event, data=data, to=to, room=room, - skip_sid=skip_sid, - namespace=namespace or self.namespace, - callback=callback, - ignore_queue=ignore_queue) - - async def send(self, data, to=None, room=None, skip_sid=None, - namespace=None, callback=None, ignore_queue=False): + return await self.server.emit( + event, + data=data, + to=to, + room=room, + skip_sid=skip_sid, + namespace=namespace or self.namespace, + callback=callback, + ignore_queue=ignore_queue, + ) + + async def send( + self, + data, + to=None, + room=None, + skip_sid=None, + namespace=None, + callback=None, + ignore_queue=False, + ): """Send a message to one or more connected clients. The only difference with the :func:`socketio.Server.send` method is @@ -83,24 +106,41 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): Note: this method is a coroutine. """ - return await self.server.send(data, to=to, room=room, - skip_sid=skip_sid, - namespace=namespace or self.namespace, - callback=callback, - ignore_queue=ignore_queue) - - async def call(self, event, data=None, to=None, sid=None, namespace=None, - timeout=None, ignore_queue=False): + return await self.server.send( + data, + to=to, + room=room, + skip_sid=skip_sid, + namespace=namespace or self.namespace, + callback=callback, + ignore_queue=ignore_queue, + ) + + async def call( + self, + event, + data=None, + to=None, + sid=None, + namespace=None, + timeout=None, + ignore_queue=False, + ): """Emit a custom event to a client and wait for the response. The only difference with the :func:`socketio.Server.call` method is that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return await self.server.call(event, data=data, to=to, sid=sid, - namespace=namespace or self.namespace, - timeout=timeout, - ignore_queue=ignore_queue) + return await self.server.call( + event, + data=data, + to=to, + sid=sid, + namespace=namespace or self.namespace, + timeout=timeout, + ignore_queue=ignore_queue, + ) async def enter_room(self, sid, room, namespace=None): """Enter a room. @@ -112,7 +152,8 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): Note: this method is a coroutine. """ return await self.server.enter_room( - sid, room, namespace=namespace or self.namespace) + sid, room, namespace=namespace or self.namespace + ) async def leave_room(self, sid, room, namespace=None): """Leave a room. @@ -124,7 +165,8 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): Note: this method is a coroutine. """ return await self.server.leave_room( - sid, room, namespace=namespace or self.namespace) + sid, room, namespace=namespace or self.namespace + ) async def close_room(self, room, namespace=None): """Close a room. @@ -135,8 +177,7 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): Note: this method is a coroutine. """ - return await self.server.close_room( - room, namespace=namespace or self.namespace) + return await self.server.close_room(room, namespace=namespace or self.namespace) async def get_session(self, sid, namespace=None): """Return the user session for a client. @@ -147,8 +188,7 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): Note: this method is a coroutine. """ - return await self.server.get_session( - sid, namespace=namespace or self.namespace) + return await self.server.get_session(sid, namespace=namespace or self.namespace) async def save_session(self, sid, session, namespace=None): """Store the user session for a client. @@ -160,7 +200,8 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): Note: this method is a coroutine. """ return await self.server.save_session( - sid, session, namespace=namespace or self.namespace) + sid, session, namespace=namespace or self.namespace + ) def session(self, sid, namespace=None): """Return the user session for a client with context manager syntax. @@ -180,8 +221,7 @@ class AsyncNamespace(base_namespace.BaseServerNamespace): Note: this method is a coroutine. """ - return await self.server.disconnect( - sid, namespace=namespace or self.namespace) + return await self.server.disconnect(sid, namespace=namespace or self.namespace) class AsyncClientNamespace(base_namespace.BaseClientNamespace): @@ -197,6 +237,7 @@ class AsyncClientNamespace(base_namespace.BaseClientNamespace): handlers defined in this class. If this argument is omitted, the default namespace is used. """ + def is_asyncio_based(self): return True @@ -210,7 +251,7 @@ class AsyncClientNamespace(base_namespace.BaseClientNamespace): Note: this method is a coroutine. """ - handler_name = 'on_' + (event or '') + handler_name = "on_" + (event or "") if hasattr(self, handler_name): handler = getattr(self, handler_name) if asyncio.iscoroutinefunction(handler) is True: @@ -220,7 +261,7 @@ class AsyncClientNamespace(base_namespace.BaseClientNamespace): except TypeError: # legacy disconnect events do not have a reason # argument - if event == 'disconnect': + if event == "disconnect": ret = await handler(*args[:-1]) else: # pragma: no cover raise @@ -232,7 +273,7 @@ class AsyncClientNamespace(base_namespace.BaseClientNamespace): except TypeError: # legacy disconnect events do not have a reason # argument - if event == 'disconnect': + if event == "disconnect": ret = handler(*args[:-1]) else: # pragma: no cover raise @@ -247,9 +288,9 @@ class AsyncClientNamespace(base_namespace.BaseClientNamespace): Note: this method is a coroutine. """ - return await self.client.emit(event, data=data, - namespace=namespace or self.namespace, - callback=callback) + return await self.client.emit( + event, data=data, namespace=namespace or self.namespace, callback=callback + ) async def send(self, data, namespace=None, callback=None): """Send a message to the server. @@ -260,9 +301,9 @@ class AsyncClientNamespace(base_namespace.BaseClientNamespace): Note: this method is a coroutine. """ - return await self.client.send(data, - namespace=namespace or self.namespace, - callback=callback) + return await self.client.send( + data, namespace=namespace or self.namespace, callback=callback + ) async def call(self, event, data=None, namespace=None, timeout=None): """Emit a custom event to the server and wait for the response. @@ -271,9 +312,9 @@ class AsyncClientNamespace(base_namespace.BaseClientNamespace): that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return await self.client.call(event, data=data, - namespace=namespace or self.namespace, - timeout=timeout) + return await self.client.call( + event, data=data, namespace=namespace or self.namespace, timeout=timeout + ) async def disconnect(self): """Disconnect a client. diff --git a/src/socketio/async_pubsub_manager.py b/src/socketio/async_pubsub_manager.py index 72946eb..c4fc13c 100644 --- a/src/socketio/async_pubsub_manager.py +++ b/src/socketio/async_pubsub_manager.py @@ -1,9 +1,9 @@ import asyncio -from functools import partial +import pickle import uuid +from functools import partial from engineio import json -import pickle from .async_manager import AsyncManager @@ -22,9 +22,10 @@ class AsyncPubSubManager(AsyncManager): :param channel: The channel name on which the server sends and receives notifications. """ - name = 'asyncpubsub' - def __init__(self, channel='socketio', write_only=False, logger=None): + name = "asyncpubsub" + + def __init__(self, channel="socketio", write_only=False, logger=None): super().__init__() self.channel = channel self.write_only = write_only @@ -35,10 +36,19 @@ class AsyncPubSubManager(AsyncManager): super().initialize() if not self.write_only: self.thread = self.server.start_background_task(self._thread) - self._get_logger().info(self.name + ' backend initialized.') + self._get_logger().info(self.name + " backend initialized.") - async def emit(self, event, data, namespace=None, room=None, skip_sid=None, - callback=None, to=None, **kwargs): + async def emit( + self, + event, + data, + namespace=None, + room=None, + skip_sid=None, + callback=None, + to=None, + **kwargs, + ): """Emit a message to a single client, a room, or all the clients connected to the namespace. @@ -50,25 +60,37 @@ class AsyncPubSubManager(AsyncManager): Note: this method is a coroutine. """ room = to or room - if kwargs.get('ignore_queue'): + if kwargs.get("ignore_queue"): return await super().emit( - event, data, namespace=namespace, room=room, skip_sid=skip_sid, - callback=callback) - namespace = namespace or '/' + event, + data, + namespace=namespace, + room=room, + skip_sid=skip_sid, + callback=callback, + ) + namespace = namespace or "/" if callback is not None: if self.server is None: - raise RuntimeError('Callbacks can only be issued from the ' - 'context of a server.') + raise RuntimeError( + "Callbacks can only be issued from the " "context of a server." + ) if room is None: - raise ValueError('Cannot use callback without a room set.') + raise ValueError("Cannot use callback without a room set.") id = self._generate_ack_id(room, callback) callback = (room, namespace, id) else: callback = None - message = {'method': 'emit', 'event': event, 'data': data, - 'namespace': namespace, 'room': room, - 'skip_sid': skip_sid, 'callback': callback, - 'host_id': self.host_id} + message = { + "method": "emit", + "event": event, + "data": data, + "namespace": namespace, + "room": room, + "skip_sid": skip_sid, + "callback": callback, + "host_id": self.host_id, + } await self._handle_emit(message) # handle in this host await self._publish(message) # notify other hosts @@ -76,43 +98,61 @@ class AsyncPubSubManager(AsyncManager): if self.is_connected(sid, namespace): # client is in this server, so we can disconnect directly return await super().can_disconnect(sid, namespace) - else: - # client is in another server, so we post request to the queue - await self._publish({'method': 'disconnect', 'sid': sid, - 'namespace': namespace or '/', - 'host_id': self.host_id}) + # client is in another server, so we post request to the queue + await self._publish( + { + "method": "disconnect", + "sid": sid, + "namespace": namespace or "/", + "host_id": self.host_id, + } + ) async def disconnect(self, sid, namespace, **kwargs): - if kwargs.get('ignore_queue'): - return await super().disconnect( - sid, namespace=namespace) - message = {'method': 'disconnect', 'sid': sid, - 'namespace': namespace or '/', 'host_id': self.host_id} + if kwargs.get("ignore_queue"): + return await super().disconnect(sid, namespace=namespace) + message = { + "method": "disconnect", + "sid": sid, + "namespace": namespace or "/", + "host_id": self.host_id, + } await self._handle_disconnect(message) # handle in this host await self._publish(message) # notify other hosts async def enter_room(self, sid, namespace, room, eio_sid=None): if self.is_connected(sid, namespace): # client is in this server, so we can disconnect directly - return await super().enter_room(sid, namespace, room, - eio_sid=eio_sid) - else: - message = {'method': 'enter_room', 'sid': sid, 'room': room, - 'namespace': namespace or '/', 'host_id': self.host_id} - await self._publish(message) # notify other hosts + return await super().enter_room(sid, namespace, room, eio_sid=eio_sid) + message = { + "method": "enter_room", + "sid": sid, + "room": room, + "namespace": namespace or "/", + "host_id": self.host_id, + } + await self._publish(message) # notify other hosts async def leave_room(self, sid, namespace, room): if self.is_connected(sid, namespace): # client is in this server, so we can disconnect directly return await super().leave_room(sid, namespace, room) - else: - message = {'method': 'leave_room', 'sid': sid, 'room': room, - 'namespace': namespace or '/', 'host_id': self.host_id} - await self._publish(message) # notify other hosts + message = { + "method": "leave_room", + "sid": sid, + "room": room, + "namespace": namespace or "/", + "host_id": self.host_id, + } + await self._publish(message) # notify other hosts async def close_room(self, room, namespace=None): - message = {'method': 'close_room', 'room': room, - 'namespace': namespace or '/', 'host_id': self.host_id} + message = { + "method": "close_room", + "room": room, + "namespace": namespace or "/", + "host_id": self.host_id, + } await self._handle_close_room(message) # handle in this host await self._publish(message) # notify other hosts @@ -122,8 +162,9 @@ class AsyncPubSubManager(AsyncManager): This method needs to be implemented by the different subclasses that support pub/sub backends. """ - raise NotImplementedError('This method must be implemented in a ' - 'subclass.') # pragma: no cover + raise NotImplementedError( + "This method must be implemented in a " "subclass." + ) # pragma: no cover async def _listen(self): """Return the next message published on the Socket.IO channel, @@ -132,67 +173,79 @@ class AsyncPubSubManager(AsyncManager): This method needs to be implemented by the different subclasses that support pub/sub backends. """ - raise NotImplementedError('This method must be implemented in a ' - 'subclass.') # pragma: no cover + raise NotImplementedError( + "This method must be implemented in a " "subclass." + ) # pragma: no cover async def _handle_emit(self, message): # Events with callbacks are very tricky to handle across hosts # Here in the receiving end we set up a local callback that preserves # the callback host and id from the sender - remote_callback = message.get('callback') - remote_host_id = message.get('host_id') + remote_callback = message.get("callback") + remote_host_id = message.get("host_id") if remote_callback is not None and len(remote_callback) == 3: - callback = partial(self._return_callback, remote_host_id, - *remote_callback) + callback = partial(self._return_callback, remote_host_id, *remote_callback) else: callback = None - await super().emit(message['event'], message['data'], - namespace=message.get('namespace'), - room=message.get('room'), - skip_sid=message.get('skip_sid'), - callback=callback) + await super().emit( + message["event"], + message["data"], + namespace=message.get("namespace"), + room=message.get("room"), + skip_sid=message.get("skip_sid"), + callback=callback, + ) async def _handle_callback(self, message): - if self.host_id == message.get('host_id'): + if self.host_id == message.get("host_id"): try: - sid = message['sid'] - id = message['id'] - args = message['args'] + sid = message["sid"] + id = message["id"] + args = message["args"] except KeyError: return await self.trigger_callback(sid, id, args) - async def _return_callback(self, host_id, sid, namespace, callback_id, - *args): + async def _return_callback(self, host_id, sid, namespace, callback_id, *args): # When an event callback is received, the callback is returned back # the sender, which is identified by the host_id if host_id == self.host_id: await self.trigger_callback(sid, callback_id, args) else: - await self._publish({'method': 'callback', 'host_id': host_id, - 'sid': sid, 'namespace': namespace, - 'id': callback_id, 'args': args}) + await self._publish( + { + "method": "callback", + "host_id": host_id, + "sid": sid, + "namespace": namespace, + "id": callback_id, + "args": args, + } + ) async def _handle_disconnect(self, message): - await self.server.disconnect(sid=message.get('sid'), - namespace=message.get('namespace'), - ignore_queue=True) + await self.server.disconnect( + sid=message.get("sid"), + namespace=message.get("namespace"), + ignore_queue=True, + ) async def _handle_enter_room(self, message): - sid = message.get('sid') - namespace = message.get('namespace') + sid = message.get("sid") + namespace = message.get("namespace") if self.is_connected(sid, namespace): - await super().enter_room(sid, namespace, message.get('room')) + await super().enter_room(sid, namespace, message.get("room")) async def _handle_leave_room(self, message): - sid = message.get('sid') - namespace = message.get('namespace') + sid = message.get("sid") + namespace = message.get("namespace") if self.is_connected(sid, namespace): - await super().leave_room(sid, namespace, message.get('room')) + await super().leave_room(sid, namespace, message.get("room")) async def _handle_close_room(self, message): - await super().close_room(room=message.get('room'), - namespace=message.get('namespace')) + await super().close_room( + room=message.get("room"), namespace=message.get("namespace") + ) async def _thread(self): while True: @@ -212,32 +265,35 @@ class AsyncPubSubManager(AsyncManager): data = json.loads(message) except: pass - if data and 'method' in data: - self._get_logger().debug('pubsub message: {}'.format( - data['method'])) + if data and "method" in data: + self._get_logger().debug( + "pubsub message: {}".format(data["method"]) + ) try: - if data['method'] == 'callback': + if data["method"] == "callback": await self._handle_callback(data) - elif data.get('host_id') != self.host_id: - if data['method'] == 'emit': + elif data.get("host_id") != self.host_id: + if data["method"] == "emit": await self._handle_emit(data) - elif data['method'] == 'disconnect': + elif data["method"] == "disconnect": await self._handle_disconnect(data) - elif data['method'] == 'enter_room': + elif data["method"] == "enter_room": await self._handle_enter_room(data) - elif data['method'] == 'leave_room': + elif data["method"] == "leave_room": await self._handle_leave_room(data) - elif data['method'] == 'close_room': + elif data["method"] == "close_room": await self._handle_close_room(data) except asyncio.CancelledError: raise # let the outer try/except handle it except Exception: self.server.logger.exception( - 'Handler error in pubsub listening thread') - self.server.logger.error('pubsub listen() exited unexpectedly') + "Handler error in pubsub listening thread" + ) + self.server.logger.error("pubsub listen() exited unexpectedly") break # loop should never exit except in unit tests! except asyncio.CancelledError: # pragma: no cover break except Exception: # pragma: no cover - self.server.logger.exception('Unexpected Error in pubsub ' - 'listening thread') + self.server.logger.exception( + "Unexpected Error in pubsub " "listening thread" + ) diff --git a/src/socketio/async_redis_manager.py b/src/socketio/async_redis_manager.py index 92109a2..35fc935 100644 --- a/src/socketio/async_redis_manager.py +++ b/src/socketio/async_redis_manager.py @@ -25,9 +25,8 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover To use a Redis backend, initialize the :class:`AsyncServer` instance as follows:: - url = 'redis://hostname:port/0' - server = socketio.AsyncServer( - client_manager=socketio.AsyncRedisManager(url)) + url = "redis://hostname:port/0" + server = socketio.AsyncServer(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 a @@ -43,27 +42,36 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover :param redis_options: additional keyword arguments to be passed to ``Redis.from_url()`` or ``Sentinel()``. """ - name = 'aioredis' - def __init__(self, url='redis://localhost:6379/0', channel='socketio', - write_only=False, logger=None, redis_options=None): + name = "aioredis" + + def __init__( + self, + url="redis://localhost:6379/0", + channel="socketio", + write_only=False, + logger=None, + redis_options=None, + ): if aioredis is None: - raise RuntimeError('Redis package is not installed ' - '(Run "pip install redis" in your virtualenv).') - if not hasattr(aioredis.Redis, 'from_url'): - raise RuntimeError('Version 2 of aioredis package is required.') + raise RuntimeError( + "Redis package is not installed " + '(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() def _redis_connect(self): - if not self.redis_url.startswith('redis+sentinel://'): - 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) + 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) @@ -76,16 +84,13 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover try: if not retry: self._redis_connect() - return await self.redis.publish( - self.channel, pickle.dumps(data)) + return await self.redis.publish(self.channel, pickle.dumps(data)) except RedisError: if retry: - self._get_logger().error('Cannot publish to redis... ' - 'retrying') + self._get_logger().error("Cannot publish to redis... " "retrying") retry = False else: - self._get_logger().error('Cannot publish to redis... ' - 'giving up') + self._get_logger().error("Cannot publish to redis... " "giving up") break async def _redis_listen_with_retries(self): @@ -100,9 +105,9 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover async for message in self.pubsub.listen(): yield message except RedisError: - self._get_logger().error('Cannot receive from redis... ' - 'retrying in ' - '{} secs'.format(retry_sleep)) + self._get_logger().error( + "Cannot receive from redis... " "retrying in " f"{retry_sleep} secs" + ) connect = True await asyncio.sleep(retry_sleep) retry_sleep *= 2 @@ -110,10 +115,13 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover retry_sleep = 60 async def _listen(self): - channel = self.channel.encode('utf-8') + 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: - yield message['data'] + if ( + message["channel"] == channel + and message["type"] == "message" + and "data" in message + ): + yield message["data"] await self.pubsub.unsubscribe(self.channel) diff --git a/src/socketio/async_server.py b/src/socketio/async_server.py index fac0f2b..c575abc 100644 --- a/src/socketio/async_server.py +++ b/src/socketio/async_server.py @@ -2,10 +2,7 @@ import asyncio import engineio -from . import async_manager -from . import base_server -from . import exceptions -from . import packet +from . import async_manager, base_server, exceptions, packet # this set is used to keep references to background tasks to prevent them from # being garbage collected mid-execution. Solution taken from @@ -111,23 +108,45 @@ class AsyncServer(base_server.BaseServer): fatal errors are logged even when ``engineio_logger`` is ``False``. """ - def __init__(self, client_manager=None, logger=False, json=None, - async_handlers=True, namespaces=None, **kwargs): + + def __init__( + self, + client_manager=None, + logger=False, + json=None, + async_handlers=True, + namespaces=None, + **kwargs, + ): if client_manager is None: client_manager = async_manager.AsyncManager() - super().__init__(client_manager=client_manager, logger=logger, - json=json, async_handlers=async_handlers, - namespaces=namespaces, **kwargs) + super().__init__( + client_manager=client_manager, + logger=logger, + json=json, + async_handlers=async_handlers, + namespaces=namespaces, + **kwargs, + ) def is_asyncio_based(self): return True - def attach(self, app, socketio_path='socket.io'): + def attach(self, app, socketio_path="socket.io"): """Attach the Socket.IO server to an application.""" self.eio.attach(app, socketio_path) - async def emit(self, event, data=None, to=None, room=None, skip_sid=None, - namespace=None, callback=None, ignore_queue=False): + async def emit( + self, + event, + data=None, + to=None, + room=None, + skip_sid=None, + namespace=None, + callback=None, + ignore_queue=False, + ): """Emit a custom event to one or more connected clients. :param event: The event name. It can be any string. The event names @@ -171,16 +190,31 @@ class AsyncServer(base_server.BaseServer): Note 2: this method is a coroutine. """ - namespace = namespace or '/' + namespace = namespace or "/" room = to or room - self.logger.info('emitting event "%s" to %s [%s]', event, - room or 'all', namespace) - await self.manager.emit(event, data, namespace, room=room, - skip_sid=skip_sid, callback=callback, - ignore_queue=ignore_queue) - - async def send(self, data, to=None, room=None, skip_sid=None, - namespace=None, callback=None, ignore_queue=False): + self.logger.info( + 'emitting event "%s" to %s [%s]', event, room or "all", namespace + ) + await self.manager.emit( + event, + data, + namespace, + room=room, + skip_sid=skip_sid, + callback=callback, + ignore_queue=ignore_queue, + ) + + async def send( + self, + data, + to=None, + room=None, + skip_sid=None, + namespace=None, + callback=None, + ignore_queue=False, + ): """Send a message to one or more connected clients. This function emits an event with the name ``'message'``. Use @@ -218,12 +252,27 @@ class AsyncServer(base_server.BaseServer): Note: this method is a coroutine. """ - await self.emit('message', data=data, to=to, room=room, - skip_sid=skip_sid, namespace=namespace, - callback=callback, ignore_queue=ignore_queue) - - async def call(self, event, data=None, to=None, sid=None, namespace=None, - timeout=60, ignore_queue=False): + await self.emit( + "message", + data=data, + to=to, + room=room, + skip_sid=skip_sid, + namespace=namespace, + callback=callback, + ignore_queue=ignore_queue, + ) + + async def call( + self, + event, + data=None, + to=None, + sid=None, + namespace=None, + timeout=60, + ignore_queue=False, + ): """Emit a custom event to a client and wait for the response. This method issues an emit with a callback and waits for the callback @@ -264,10 +313,9 @@ class AsyncServer(base_server.BaseServer): Note 2: this method is a coroutine. """ if to is None and sid is None: - raise ValueError('Cannot use call() to broadcast.') + raise ValueError("Cannot use call() to broadcast.") if not self.async_handlers: - raise RuntimeError( - 'Cannot use call() when async_handlers is False.') + raise RuntimeError("Cannot use call() when async_handlers is False.") callback_event = self.eio.create_event() callback_args = [] @@ -275,15 +323,25 @@ class AsyncServer(base_server.BaseServer): callback_args.append(args) callback_event.set() - await self.emit(event, data=data, room=to or sid, namespace=namespace, - callback=event_callback, ignore_queue=ignore_queue) + await self.emit( + event, + data=data, + room=to or sid, + namespace=namespace, + callback=event_callback, + ignore_queue=ignore_queue, + ) try: await asyncio.wait_for(callback_event.wait(), timeout) except asyncio.TimeoutError: raise exceptions.TimeoutError() from None - return callback_args[0] if len(callback_args[0]) > 1 \ - else callback_args[0][0] if len(callback_args[0]) == 1 \ + return ( + callback_args[0] + if len(callback_args[0]) > 1 + else callback_args[0][0] + if len(callback_args[0]) == 1 else None + ) async def enter_room(self, sid, room, namespace=None): """Enter a room. @@ -299,8 +357,8 @@ class AsyncServer(base_server.BaseServer): Note: this method is a coroutine. """ - namespace = namespace or '/' - self.logger.info('%s is entering room %s [%s]', sid, room, namespace) + namespace = namespace or "/" + self.logger.info("%s is entering room %s [%s]", sid, room, namespace) await self.manager.enter_room(sid, namespace, room) async def leave_room(self, sid, room, namespace=None): @@ -315,8 +373,8 @@ class AsyncServer(base_server.BaseServer): Note: this method is a coroutine. """ - namespace = namespace or '/' - self.logger.info('%s is leaving room %s [%s]', sid, room, namespace) + namespace = namespace or "/" + self.logger.info("%s is leaving room %s [%s]", sid, room, namespace) await self.manager.leave_room(sid, namespace, room) async def close_room(self, room, namespace=None): @@ -330,8 +388,8 @@ class AsyncServer(base_server.BaseServer): Note: this method is a coroutine. """ - namespace = namespace or '/' - self.logger.info('room %s is closing [%s]', room, namespace) + namespace = namespace or "/" + self.logger.info("room %s is closing [%s]", room, namespace) await self.manager.close_room(room, namespace) async def get_session(self, sid, namespace=None): @@ -345,7 +403,7 @@ class AsyncServer(base_server.BaseServer): dictionary are not guaranteed to be preserved. If you want to modify the user session, use the ``session`` context manager instead. """ - namespace = namespace or '/' + namespace = namespace or "/" eio_sid = self.manager.eio_sid_from_sid(sid, namespace) eio_session = await self.eio.get_session(eio_sid) return eio_session.setdefault(namespace, {}) @@ -358,7 +416,7 @@ class AsyncServer(base_server.BaseServer): :param namespace: The Socket.IO namespace. If this argument is omitted the default namespace is used. """ - namespace = namespace or '/' + namespace = namespace or "/" eio_sid = self.manager.eio_sid_from_sid(sid, namespace) eio_session = await self.eio.get_session(eio_sid) eio_session[namespace] = session @@ -372,19 +430,21 @@ class AsyncServer(base_server.BaseServer): the client. Any changes that are made to this dictionary inside the context manager block are saved back to the session. Example usage:: - @eio.on('connect') + @eio.on("connect") async def on_connect(sid, environ): username = authenticate_user(environ) if not username: return False async with eio.session(sid) as session: - session['username'] = username + session["username"] = username + - @eio.on('message') + @eio.on("message") async def on_message(sid, msg): async with eio.session(sid) as session: - print('received message from ', session['username']) + print("received message from ", session["username"]) """ + class _session_context_manager: def __init__(self, server, sid, namespace): self.server = server @@ -394,12 +454,14 @@ class AsyncServer(base_server.BaseServer): async def __aenter__(self): self.session = await self.server.get_session( - sid, namespace=self.namespace) + sid, namespace=self.namespace + ) return self.session async def __aexit__(self, *args): - await self.server.save_session(sid, self.session, - namespace=self.namespace) + await self.server.save_session( + sid, self.session, namespace=self.namespace + ) return _session_context_manager(self, sid, namespace) @@ -417,20 +479,21 @@ class AsyncServer(base_server.BaseServer): Note: this method is a coroutine. """ - namespace = namespace or '/' + namespace = namespace or "/" if ignore_queue: delete_it = self.manager.is_connected(sid, namespace) else: delete_it = await self.manager.can_disconnect(sid, namespace) if delete_it: - self.logger.info('Disconnecting %s [%s]', sid, namespace) + self.logger.info("Disconnecting %s [%s]", sid, namespace) 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, - self.reason.SERVER_DISCONNECT) - await self.manager.disconnect(sid, namespace=namespace, - ignore_queue=True) + await self._send_packet( + eio_sid, self.packet_class(packet.DISCONNECT, namespace=namespace) + ) + await self._trigger_event( + "disconnect", namespace, sid, self.reason.SERVER_DISCONNECT + ) + await self.manager.disconnect(sid, namespace=namespace, ignore_queue=True) async def shutdown(self): """Stop Socket.IO background tasks. @@ -438,7 +501,7 @@ class AsyncServer(base_server.BaseServer): This method stops all background activity initiated by the Socket.IO server. It must be called before shutting down the web server. """ - self.logger.info('Socket.IO is shutting down') + self.logger.info("Socket.IO is shutting down") await self.eio.shutdown() async def handle_request(self, *args, **kwargs): @@ -478,9 +541,15 @@ class AsyncServer(base_server.BaseServer): """ return await self.eio.sleep(seconds) - def instrument(self, auth=None, mode='development', read_only=False, - server_id=None, namespace='/admin', - server_stats_interval=2): + def instrument( + self, + auth=None, + mode="development", + read_only=False, + server_id=None, + namespace="/admin", + server_stats_interval=2, + ): """Instrument the Socket.IO server for monitoring with the `Socket.IO Admin UI `_. @@ -512,10 +581,16 @@ class AsyncServer(base_server.BaseServer): connected admins. """ from .async_admin import InstrumentedAsyncServer + return InstrumentedAsyncServer( - self, auth=auth, mode=mode, read_only=read_only, - server_id=server_id, namespace=namespace, - server_stats_interval=server_stats_interval) + self, + auth=auth, + mode=mode, + read_only=read_only, + server_id=server_id, + namespace=namespace, + server_stats_interval=server_stats_interval, + ) async def _send_packet(self, eio_sid, pkt): """Send a Socket.IO packet to a client.""" @@ -532,32 +607,44 @@ class AsyncServer(base_server.BaseServer): async def _handle_connect(self, eio_sid, namespace, data): """Handle a client connection request.""" - namespace = namespace or '/' + namespace = namespace or "/" sid = None - if namespace in self.handlers or namespace in self.namespace_handlers \ - or self.namespaces == '*' or namespace in self.namespaces: + if ( + namespace in self.handlers + or namespace in self.namespace_handlers + or self.namespaces == "*" + or namespace in self.namespaces + ): sid = await self.manager.connect(eio_sid, namespace) if sid is None: - await self._send_packet(eio_sid, self.packet_class( - packet.CONNECT_ERROR, data='Unable to connect', - namespace=namespace)) + await self._send_packet( + eio_sid, + self.packet_class( + packet.CONNECT_ERROR, data="Unable to connect", namespace=namespace + ), + ) return if self.always_connect: - await self._send_packet(eio_sid, self.packet_class( - packet.CONNECT, {'sid': sid}, namespace=namespace)) + await self._send_packet( + eio_sid, + self.packet_class(packet.CONNECT, {"sid": sid}, namespace=namespace), + ) fail_reason = exceptions.ConnectionRefusedError().error_args try: if data: success = await self._trigger_event( - 'connect', namespace, sid, self.environ[eio_sid], data) + "connect", namespace, sid, self.environ[eio_sid], data + ) else: try: success = await self._trigger_event( - 'connect', namespace, sid, self.environ[eio_sid]) + "connect", namespace, sid, self.environ[eio_sid] + ) except TypeError: success = await self._trigger_event( - 'connect', namespace, sid, self.environ[eio_sid], None) + "connect", namespace, sid, self.environ[eio_sid], None + ) except exceptions.ConnectionRefusedError as exc: fail_reason = exc.error_args success = False @@ -565,50 +652,56 @@ class AsyncServer(base_server.BaseServer): if success is False: if self.always_connect: self.manager.pre_disconnect(sid, namespace) - await self._send_packet(eio_sid, self.packet_class( - packet.DISCONNECT, data=fail_reason, namespace=namespace)) + await self._send_packet( + eio_sid, + self.packet_class( + packet.DISCONNECT, data=fail_reason, namespace=namespace + ), + ) else: - await self._send_packet(eio_sid, self.packet_class( - packet.CONNECT_ERROR, data=fail_reason, - namespace=namespace)) + await self._send_packet( + eio_sid, + self.packet_class( + packet.CONNECT_ERROR, data=fail_reason, namespace=namespace + ), + ) await self.manager.disconnect(sid, namespace, ignore_queue=True) elif not self.always_connect: - await self._send_packet(eio_sid, self.packet_class( - packet.CONNECT, {'sid': sid}, namespace=namespace)) + await self._send_packet( + eio_sid, + self.packet_class(packet.CONNECT, {"sid": sid}, namespace=namespace), + ) async def _handle_disconnect(self, eio_sid, namespace, reason=None): """Handle a client disconnect.""" - namespace = namespace or '/' + 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, - reason or self.reason.CLIENT_DISCONNECT) + 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): """Handle an incoming client event.""" - namespace = namespace or '/' + namespace = namespace or "/" sid = self.manager.sid_from_eio_sid(eio_sid, namespace) - self.logger.info('received event "%s" from %s [%s]', data[0], sid, - namespace) + self.logger.info('received event "%s" from %s [%s]', data[0], sid, namespace) if not self.manager.is_connected(sid, namespace): - self.logger.warning('%s is not connected to namespace %s', - sid, namespace) + self.logger.warning("%s is not connected to namespace %s", sid, namespace) return if self.async_handlers: task = self.start_background_task( - self._handle_event_internal, self, sid, eio_sid, data, - namespace, id) + self._handle_event_internal, self, sid, eio_sid, data, namespace, id + ) task_reference_holder.add(task) task.add_done_callback(task_reference_holder.discard) else: - await self._handle_event_internal(self, sid, eio_sid, data, - namespace, id) + await self._handle_event_internal(self, sid, eio_sid, data, namespace, id) - async def _handle_event_internal(self, server, sid, eio_sid, data, - namespace, id): + async def _handle_event_internal(self, server, sid, eio_sid, data, namespace, id): r = await server._trigger_event(data[0], namespace, sid, *data[1:]) if r != self.not_handled and id is not None: # send ACK packet with the response returned by the handler @@ -619,14 +712,16 @@ class AsyncServer(base_server.BaseServer): data = list(r) else: data = [r] - await server._send_packet(eio_sid, self.packet_class( - packet.ACK, namespace=namespace, id=id, data=data)) + await server._send_packet( + eio_sid, + self.packet_class(packet.ACK, namespace=namespace, id=id, data=data), + ) async def _handle_ack(self, eio_sid, namespace, id, data): """Handle ACK packets from the client.""" - namespace = namespace or '/' + namespace = namespace or "/" sid = self.manager.sid_from_eio_sid(eio_sid, namespace) - self.logger.info('received ack from %s [%s]', sid, namespace) + self.logger.info("received ack from %s [%s]", sid, namespace) await self.manager.trigger_callback(sid, id, data) async def _trigger_event(self, event, namespace, *args): @@ -640,7 +735,7 @@ class AsyncServer(base_server.BaseServer): ret = await handler(*args) except TypeError: # legacy disconnect events use only one argument - if event == 'disconnect': + if event == "disconnect": ret = await handler(*args[:-1]) else: # pragma: no cover raise @@ -651,7 +746,7 @@ class AsyncServer(base_server.BaseServer): ret = handler(*args) except TypeError: # legacy disconnect events use only one argument - if event == 'disconnect': + if event == "disconnect": ret = handler(*args[:-1]) else: # pragma: no cover raise @@ -660,8 +755,7 @@ class AsyncServer(base_server.BaseServer): handler, args = self._get_namespace_handler(namespace, args) if handler: return await handler.trigger_event(event, *args) - else: - return self.not_handled + return self.not_handled async def _handle_eio_connect(self, eio_sid, environ): """Handle the Engine.IO connection event.""" @@ -677,31 +771,30 @@ class AsyncServer(base_server.BaseServer): if pkt.add_attachment(data): del self._binary_packet[eio_sid] if pkt.packet_type == packet.BINARY_EVENT: - await self._handle_event(eio_sid, pkt.namespace, pkt.id, - pkt.data) + await self._handle_event(eio_sid, pkt.namespace, pkt.id, pkt.data) else: - await self._handle_ack(eio_sid, pkt.namespace, pkt.id, - pkt.data) + await self._handle_ack(eio_sid, pkt.namespace, pkt.id, pkt.data) else: pkt = self.packet_class(encoded_packet=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, - self.reason.CLIENT_DISCONNECT) + 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) + await self._handle_event(eio_sid, pkt.namespace, pkt.id, pkt.data) elif pkt.packet_type == packet.ACK: - await self._handle_ack(eio_sid, pkt.namespace, pkt.id, - pkt.data) - elif pkt.packet_type == packet.BINARY_EVENT or \ - pkt.packet_type == packet.BINARY_ACK: + await self._handle_ack(eio_sid, pkt.namespace, pkt.id, pkt.data) + elif ( + pkt.packet_type == packet.BINARY_EVENT + or pkt.packet_type == packet.BINARY_ACK + ): self._binary_packet[eio_sid] = pkt elif pkt.packet_type == packet.CONNECT_ERROR: - raise ValueError('Unexpected CONNECT_ERROR packet.') + raise ValueError("Unexpected CONNECT_ERROR packet.") else: - raise ValueError('Unknown packet type.') + raise ValueError("Unknown packet type.") async def _handle_eio_disconnect(self, eio_sid, reason): """Handle Engine.IO disconnect event.""" @@ -710,5 +803,5 @@ class AsyncServer(base_server.BaseServer): if eio_sid in self.environ: del self.environ[eio_sid] - def _engineio_server_class(self): + def _engineio_server_class(self) -> engineio.AsyncServer: return engineio.AsyncServer diff --git a/src/socketio/async_simple_client.py b/src/socketio/async_simple_client.py index adac6ea..3c51d48 100644 --- a/src/socketio/async_simple_client.py +++ b/src/socketio/async_simple_client.py @@ -1,6 +1,7 @@ import asyncio + from socketio import AsyncClient -from socketio.exceptions import SocketIOError, TimeoutError, DisconnectedError +from socketio.exceptions import DisconnectedError, SocketIOError, TimeoutError class AsyncSimpleClient: @@ -12,21 +13,29 @@ 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 self.client = None - self.namespace = '/' + self.namespace = "/" self.connected_event = asyncio.Event() self.connected = False self.input_event = asyncio.Event() self.input_buffer = [] - async def connect(self, url, headers={}, auth=None, transports=None, - namespace='/', socketio_path='socket.io', - wait_timeout=5): + async def connect( + self, + url, + headers={}, + auth=None, + transports=None, + namespace="/", + socketio_path="socket.io", + wait_timeout=5, + ): """Connect to a Socket.IO server. :param url: The URL of the Socket.IO server. It can include custom @@ -58,12 +67,11 @@ class AsyncSimpleClient: Note: this method is a coroutine. """ if self.connected: - raise RuntimeError('Already connected') + raise RuntimeError("Already connected") self.namespace = namespace self.input_buffer = [] self.input_event.clear() - self.client = self.client_class( - *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 @@ -79,15 +87,20 @@ class AsyncSimpleClient: self.connected = False self.connected_event.set() - @self.client.on('*', namespace=self.namespace) + @self.client.on("*", namespace=self.namespace) def on_event(event, *args): # pragma: no cover self.input_buffer.append([event, *args]) self.input_event.set() await self.client.connect( - url, headers=headers, auth=auth, transports=transports, - namespaces=[namespace], socketio_path=socketio_path, - wait_timeout=wait_timeout) + url, + headers=headers, + auth=auth, + transports=transports, + namespaces=[namespace], + socketio_path=socketio_path, + wait_timeout=wait_timeout, + ) @property def sid(self): @@ -105,7 +118,7 @@ class AsyncSimpleClient: 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. @@ -130,8 +143,7 @@ class AsyncSimpleClient: if not self.connected: raise DisconnectedError() try: - return await self.client.emit(event, data, - namespace=self.namespace) + return await self.client.emit(event, data, namespace=self.namespace) except SocketIOError: pass @@ -160,9 +172,9 @@ class AsyncSimpleClient: if not self.connected: raise DisconnectedError() try: - return await self.client.call(event, data, - namespace=self.namespace, - timeout=timeout) + return await self.client.call( + event, data, namespace=self.namespace, timeout=timeout + ) except SocketIOError: pass @@ -181,15 +193,13 @@ class AsyncSimpleClient: """ while not self.input_buffer: try: - await asyncio.wait_for(self.connected_event.wait(), - timeout=timeout) + await asyncio.wait_for(self.connected_event.wait(), timeout=timeout) except asyncio.TimeoutError: # pragma: no cover raise TimeoutError() if not self.connected: raise DisconnectedError() try: - await asyncio.wait_for(self.input_event.wait(), - timeout=timeout) + await asyncio.wait_for(self.input_event.wait(), timeout=timeout) except asyncio.TimeoutError: raise TimeoutError() self.input_event.clear() diff --git a/src/socketio/base_client.py b/src/socketio/base_client.py index 7bf4420..cd7ef6b 100644 --- a/src/socketio/base_client.py +++ b/src/socketio/base_client.py @@ -5,10 +5,9 @@ import threading import engineio -from . import base_namespace -from . import packet +from . import base_namespace, packet -default_logger = logging.getLogger('socketio.client') +default_logger = logging.getLogger("socketio.client") reconnecting_clients = [] @@ -22,28 +21,38 @@ def signal_handler(sig, frame): # pragma: no cover client._reconnect_abort.set() if callable(original_signal_handler): return original_signal_handler(sig, frame) - else: # pragma: no cover - # Handle case where no original SIGINT handler was present. - return signal.default_int_handler(sig, frame) + # pragma: no cover + # Handle case where no original SIGINT handler was present. + return signal.default_int_handler(sig, frame) original_signal_handler = None class BaseClient: - reserved_events = ['connect', 'connect_error', 'disconnect', - '__disconnect_final'] + 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, - randomization_factor=0.5, logger=False, serializer='default', - json=None, handle_sigint=True, **kwargs): + def __init__( + self, + reconnection=True, + reconnection_attempts=0, + reconnection_delay=1, + reconnection_delay_max=5, + randomization_factor=0.5, + logger=False, + serializer="default", + json=None, + handle_sigint=True, + **kwargs, + ): global original_signal_handler - if handle_sigint and original_signal_handler is None and \ - threading.current_thread() == threading.main_thread(): - original_signal_handler = signal.signal(signal.SIGINT, - signal_handler) + if ( + handle_sigint + and original_signal_handler is None + and threading.current_thread() == threading.main_thread() + ): + original_signal_handler = signal.signal(signal.SIGINT, signal_handler) self.reconnection = reconnection self.reconnection_attempts = reconnection_attempts self.reconnection_delay = reconnection_delay @@ -52,25 +61,26 @@ class BaseClient: self.handle_sigint = handle_sigint engineio_options = kwargs - engineio_options['handle_sigint'] = handle_sigint - engineio_logger = engineio_options.pop('engineio_logger', None) + engineio_options["handle_sigint"] = handle_sigint + engineio_logger = engineio_options.pop("engineio_logger", None) if engineio_logger is not None: - engineio_options['logger'] = engineio_logger - if serializer == 'default': + engineio_options["logger"] = engineio_logger + if serializer == "default": self.packet_class = packet.Packet - elif serializer == 'msgpack': + elif serializer == "msgpack": from . import msgpack_packet + self.packet_class = msgpack_packet.MsgPackPacket else: self.packet_class = serializer if json is not None: self.packet_class.json = json - engineio_options['json'] = json + engineio_options["json"] = json self.eio = self._engineio_client_class()(**engineio_options) - self.eio.on('connect', self._handle_eio_connect) - self.eio.on('message', self._handle_eio_message) - self.eio.on('disconnect', self._handle_eio_disconnect) + self.eio.on("connect", self._handle_eio_connect) + self.eio.on("message", self._handle_eio_message) + self.eio.on("disconnect", self._handle_eio_disconnect) if not isinstance(logger, bool): self.logger = logger @@ -122,15 +132,18 @@ class BaseClient: Example usage:: # as a decorator: - @sio.on('connect') + @sio.on("connect") def connect_handler(): - print('Connected!') + print("Connected!") + # as a method: def message_handler(msg): - print('Received message: ', msg) - sio.send( 'response') - sio.on('message', message_handler) + print("Received message: ", msg) + sio.send("response") + + + sio.on("message", message_handler) The arguments passed to the handler function depend on the event type: @@ -148,7 +161,7 @@ class BaseClient: the event name as first argument and the namespace as second argument, followed by any arguments specific to the event. """ - namespace = namespace or '/' + namespace = namespace or "/" def set_handler(handler): if namespace not in self.handlers: @@ -170,30 +183,30 @@ class BaseClient: @sio.event def my_event(data): - print('Received data: ', data) + print("Received data: ", data) The above example is equivalent to:: - @sio.on('my_event') + @sio.on("my_event") def my_event(data): - print('Received data: ', data) + print("Received data: ", data) A custom namespace can be given as an argument to the decorator:: - @sio.event(namespace='/test') + @sio.event(namespace="/test") def my_event(data): - print('Received data: ', data) + print("Received data: ", data) """ if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): # the decorator was invoked without arguments # args[0] is the decorated function return self.on(args[0].__name__)(args[0]) - else: - # the decorator was invoked with arguments - def set_handler(handler): - return self.on(handler.__name__, *args, **kwargs)(handler) - return set_handler + # the decorator was invoked with arguments + def set_handler(handler): + return self.on(handler.__name__, *args, **kwargs)(handler) + + return set_handler def register_namespace(self, namespace_handler): """Register a namespace handler object. @@ -202,14 +215,12 @@ class BaseClient: subclass that handles all the event traffic for a namespace. """ - if not isinstance(namespace_handler, - base_namespace.BaseClientNamespace): - raise ValueError('Not a namespace instance') + if not isinstance(namespace_handler, base_namespace.BaseClientNamespace): + raise ValueError("Not a namespace instance") if self.is_asyncio_based() != namespace_handler.is_asyncio_based(): - raise ValueError('Not a valid namespace class for this client') + raise ValueError("Not a valid namespace class for this client") namespace_handler._set_client(self) - self.namespace_handlers[namespace_handler.namespace] = \ - namespace_handler + self.namespace_handlers[namespace_handler.namespace] = namespace_handler def get_sid(self, namespace=None): """Return the ``sid`` associated with a connection. @@ -223,7 +234,7 @@ class BaseClient: This method returns the ``sid`` for the requested namespace as a string. """ - return self.namespaces.get(namespace or '/') + return self.namespaces.get(namespace or "/") def transport(self): """Return the name of the transport used by the client. @@ -245,17 +256,15 @@ class BaseClient: if namespace in self.handlers: if event in self.handlers[namespace]: handler = self.handlers[namespace][event] - elif event not in self.reserved_events and \ - '*' in self.handlers[namespace]: - handler = self.handlers[namespace]['*'] + elif event not in self.reserved_events and "*" in self.handlers[namespace]: + handler = self.handlers[namespace]["*"] args = (event, *args) - elif '*' in self.handlers: - if event in self.handlers['*']: - handler = self.handlers['*'][event] + elif "*" in self.handlers: + if event in self.handlers["*"]: + handler = self.handlers["*"][event] args = (namespace, *args) - elif event not in self.reserved_events and \ - '*' in self.handlers['*']: - handler = self.handlers['*']['*'] + elif event not in self.reserved_events and "*" in self.handlers["*"]: + handler = self.handlers["*"]["*"] args = (event, namespace, *args) return handler, args @@ -268,14 +277,14 @@ class BaseClient: handler = None if namespace in self.namespace_handlers: handler = self.namespace_handlers[namespace] - elif '*' in self.namespace_handlers: - handler = self.namespace_handlers['*'] + elif "*" in self.namespace_handlers: + handler = self.namespace_handlers["*"] args = (namespace, *args) return handler, args def _generate_ack_id(self, namespace, callback): """Generate a unique identifier for an ACK packet.""" - namespace = namespace or '/' + namespace = namespace or "/" if namespace not in self.callbacks: self.callbacks[namespace] = {0: itertools.count(1)} id = next(self.callbacks[namespace][0]) @@ -283,13 +292,13 @@ class BaseClient: return id def _handle_eio_connect(self): # pragma: no cover - raise NotImplementedError() + raise NotImplementedError def _handle_eio_message(self, data): # pragma: no cover - raise NotImplementedError() + raise NotImplementedError def _handle_eio_disconnect(self, reason): # pragma: no cover - raise NotImplementedError() + raise NotImplementedError def _engineio_client_class(self): # pragma: no cover - raise NotImplementedError() + raise NotImplementedError diff --git a/src/socketio/base_manager.py b/src/socketio/base_manager.py index dafa60a..e332972 100644 --- a/src/socketio/base_manager.py +++ b/src/socketio/base_manager.py @@ -1,9 +1,9 @@ import itertools import logging -from bidict import bidict, ValueDuplicationError +from bidict import ValueDuplicationError, bidict -default_logger = logging.getLogger('socketio') +default_logger = logging.getLogger("socketio") class BaseManager: @@ -31,7 +31,7 @@ class BaseManager: def get_participants(self, namespace, room): """Return an iterable with the active participants in a room.""" ns = self.rooms.get(namespace, {}) - if hasattr(room, '__len__') and not isinstance(room, str): + if hasattr(room, "__len__") and not isinstance(room, str): participants = ns[room[0]]._fwdm.copy() if room[0] in ns else {} for r in room[1:]: participants.update(ns[r]._fwdm if r in ns else {}) @@ -51,8 +51,10 @@ class BaseManager: return sid def is_connected(self, sid, namespace): - if namespace in self.pending_disconnect and \ - sid in self.pending_disconnect[namespace]: + if ( + namespace in self.pending_disconnect + and sid in self.pending_disconnect[namespace] + ): # the client is in the process of being disconnected return False try: @@ -94,15 +96,17 @@ class BaseManager: self.basic_leave_room(sid, namespace, room) if sid in self.callbacks: del self.callbacks[sid] - if namespace in self.pending_disconnect and \ - sid in self.pending_disconnect[namespace]: + if ( + namespace in self.pending_disconnect + and sid in self.pending_disconnect[namespace] + ): self.pending_disconnect[namespace].remove(sid) if len(self.pending_disconnect[namespace]) == 0: del self.pending_disconnect[namespace] def basic_enter_room(self, sid, namespace, room, eio_sid=None): if eio_sid is None and namespace not in self.rooms: - raise ValueError('sid is not connected to requested namespace') + raise ValueError("sid is not connected to requested namespace") if namespace not in self.rooms: self.rooms[namespace] = {} if room not in self.rooms[namespace]: @@ -152,10 +156,8 @@ class BaseManager: Prevents uninitialized servers in write-only mode from failing. """ - if self.logger: return self.logger - elif self.server: + if self.server: return self.server.logger - else: - return default_logger + return default_logger diff --git a/src/socketio/base_namespace.py b/src/socketio/base_namespace.py index 14b5d8f..bf0e13d 100644 --- a/src/socketio/base_namespace.py +++ b/src/socketio/base_namespace.py @@ -1,6 +1,6 @@ class BaseNamespace: def __init__(self, namespace=None): - self.namespace = namespace or '/' + self.namespace = namespace or "/" def is_asyncio_based(self): return False diff --git a/src/socketio/base_server.py b/src/socketio/base_server.py index d134eba..45083fe 100644 --- a/src/socketio/base_server.py +++ b/src/socketio/base_server.py @@ -2,39 +2,46 @@ import logging import engineio -from . import manager -from . import base_namespace -from . import packet +from . import base_namespace, manager, packet -default_logger = logging.getLogger('socketio.server') +default_logger = logging.getLogger("socketio.server") class BaseServer: - reserved_events = ['connect', 'disconnect'] + 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, - namespaces=None, **kwargs): + def __init__( + self, + client_manager=None, + logger=False, + serializer="default", + json=None, + async_handlers=True, + always_connect=False, + namespaces=None, + **kwargs, + ): engineio_options = kwargs - engineio_logger = engineio_options.pop('engineio_logger', None) + engineio_logger = engineio_options.pop("engineio_logger", None) if engineio_logger is not None: - engineio_options['logger'] = engineio_logger - if serializer == 'default': + engineio_options["logger"] = engineio_logger + if serializer == "default": self.packet_class = packet.Packet - elif serializer == 'msgpack': + elif serializer == "msgpack": from . import msgpack_packet + self.packet_class = msgpack_packet.MsgPackPacket else: self.packet_class = serializer if json is not None: self.packet_class.json = json - engineio_options['json'] = json - engineio_options['async_handlers'] = False + engineio_options["json"] = json + engineio_options["async_handlers"] = False self.eio = self._engineio_server_class()(**engineio_options) - self.eio.on('connect', self._handle_eio_connect) - self.eio.on('message', self._handle_eio_message) - self.eio.on('disconnect', self._handle_eio_disconnect) + self.eio.on("connect", self._handle_eio_connect) + self.eio.on("message", self._handle_eio_message) + self.eio.on("disconnect", self._handle_eio_disconnect) self.environ = {} self.handlers = {} @@ -62,7 +69,7 @@ class BaseServer: self.async_handlers = async_handlers self.always_connect = always_connect - self.namespaces = namespaces or ['/'] + self.namespaces = namespaces or ["/"] self.async_mode = self.eio.async_mode @@ -87,17 +94,20 @@ class BaseServer: Example usage:: # as a decorator: - @sio.on('connect', namespace='/chat') + @sio.on("connect", namespace="/chat") def connect_handler(sid, environ): - print('Connection request') - if environ['REMOTE_ADDR'] in blacklisted: + print("Connection request") + if environ["REMOTE_ADDR"] in blacklisted: return False # reject + # as a method: def message_handler(sid, msg): - print('Received message: ', msg) - sio.send(sid, 'response') - socket_io.on('message', namespace='/chat', handler=message_handler) + print("Received message: ", msg) + sio.send(sid, "response") + + + socket_io.on("message", namespace="/chat", handler=message_handler) The arguments passed to the handler function depend on the event type: @@ -117,7 +127,7 @@ class BaseServer: the event name as first argument and the namespace as second argument, followed by any arguments specific to the event. """ - namespace = namespace or '/' + namespace = namespace or "/" def set_handler(handler): if namespace not in self.handlers: @@ -139,30 +149,30 @@ class BaseServer: @sio.event def my_event(data): - print('Received data: ', data) + print("Received data: ", data) The above example is equivalent to:: - @sio.on('my_event') + @sio.on("my_event") def my_event(data): - print('Received data: ', data) + print("Received data: ", data) A custom namespace can be given as an argument to the decorator:: - @sio.event(namespace='/test') + @sio.event(namespace="/test") def my_event(data): - print('Received data: ', data) + print("Received data: ", data) """ if len(args) == 1 and len(kwargs) == 0 and callable(args[0]): # the decorator was invoked without arguments # args[0] is the decorated function return self.on(args[0].__name__)(args[0]) - else: - # the decorator was invoked with arguments - def set_handler(handler): - return self.on(handler.__name__, *args, **kwargs)(handler) - return set_handler + # the decorator was invoked with arguments + def set_handler(handler): + return self.on(handler.__name__, *args, **kwargs)(handler) + + return set_handler def register_namespace(self, namespace_handler): """Register a namespace handler object. @@ -171,14 +181,12 @@ class BaseServer: subclass that handles all the event traffic for a namespace. """ - if not isinstance(namespace_handler, - base_namespace.BaseServerNamespace): - raise ValueError('Not a namespace instance') + if not isinstance(namespace_handler, base_namespace.BaseServerNamespace): + raise ValueError("Not a namespace instance") if self.is_asyncio_based() != namespace_handler.is_asyncio_based(): - raise ValueError('Not a valid namespace class for this server') + raise ValueError("Not a valid namespace class for this server") namespace_handler._set_server(self) - self.namespace_handlers[namespace_handler.namespace] = \ - namespace_handler + self.namespace_handlers[namespace_handler.namespace] = namespace_handler def rooms(self, sid, namespace=None): """Return the rooms a client is in. @@ -187,7 +195,7 @@ class BaseServer: :param namespace: The Socket.IO namespace for the event. If this argument is omitted the default namespace is used. """ - namespace = namespace or '/' + namespace = namespace or "/" return self.manager.get_rooms(sid, namespace) def transport(self, sid, namespace=None): @@ -200,7 +208,7 @@ class BaseServer: :param namespace: The Socket.IO namespace. If this argument is omitted the default namespace is used. """ - eio_sid = self.manager.eio_sid_from_sid(sid, namespace or '/') + eio_sid = self.manager.eio_sid_from_sid(sid, namespace or "/") return self.eio.transport(eio_sid) def get_environ(self, sid, namespace=None): @@ -210,7 +218,7 @@ class BaseServer: :param namespace: The Socket.IO namespace. If this argument is omitted the default namespace is used. """ - eio_sid = self.manager.eio_sid_from_sid(sid, namespace or '/') + eio_sid = self.manager.eio_sid_from_sid(sid, namespace or "/") return self.environ.get(eio_sid) def _get_event_handler(self, event, namespace, args): @@ -225,17 +233,15 @@ class BaseServer: if namespace in self.handlers: if event in self.handlers[namespace]: handler = self.handlers[namespace][event] - elif event not in self.reserved_events and \ - '*' in self.handlers[namespace]: - handler = self.handlers[namespace]['*'] + elif event not in self.reserved_events and "*" in self.handlers[namespace]: + handler = self.handlers[namespace]["*"] args = (event, *args) - if handler is None and '*' in self.handlers: - if event in self.handlers['*']: - handler = self.handlers['*'][event] + if handler is None and "*" in self.handlers: + if event in self.handlers["*"]: + handler = self.handlers["*"][event] args = (namespace, *args) - elif event not in self.reserved_events and \ - '*' in self.handlers['*']: - handler = self.handlers['*']['*'] + elif event not in self.reserved_events and "*" in self.handlers["*"]: + handler = self.handlers["*"]["*"] args = (event, namespace, *args) return handler, args @@ -248,19 +254,19 @@ class BaseServer: handler = None if namespace in self.namespace_handlers: handler = self.namespace_handlers[namespace] - if handler is None and '*' in self.namespace_handlers: - handler = self.namespace_handlers['*'] + if handler is None and "*" in self.namespace_handlers: + handler = self.namespace_handlers["*"] args = (namespace, *args) return handler, args def _handle_eio_connect(self): # pragma: no cover - raise NotImplementedError() + raise NotImplementedError def _handle_eio_message(self, data): # pragma: no cover - raise NotImplementedError() + raise NotImplementedError def _handle_eio_disconnect(self): # pragma: no cover - raise NotImplementedError() + raise NotImplementedError def _engineio_server_class(self): # pragma: no cover - raise NotImplementedError('Must be implemented in subclasses') + raise NotImplementedError("Must be implemented in subclasses") diff --git a/src/socketio/client.py b/src/socketio/client.py index 84b1643..5aa40ba 100644 --- a/src/socketio/client.py +++ b/src/socketio/client.py @@ -2,9 +2,7 @@ import random import engineio -from . import base_client -from . import exceptions -from . import packet +from . import base_client, exceptions, packet class Client(base_client.BaseClient): @@ -70,9 +68,19 @@ class Client(base_client.BaseClient): fatal errors are logged even when ``engineio_logger`` is ``False``. """ - def connect(self, url, headers={}, auth=None, transports=None, - namespaces=None, socketio_path='socket.io', wait=True, - wait_timeout=1, retry=False): + + def connect( + self, + url, + headers={}, + auth=None, + transports=None, + namespaces=None, + socketio_path="socket.io", + wait=True, + wait_timeout=1, + retry=False, + ): """Connect to a Socket.IO server. :param url: The URL of the Socket.IO server. It can include custom @@ -114,10 +122,10 @@ class Client(base_client.BaseClient): Example usage:: sio = socketio.Client() - sio.connect('http://localhost:5000') + sio.connect("http://localhost:5000") """ if self.connected: - raise exceptions.ConnectionError('Already connected') + raise exceptions.ConnectionError("Already connected") self.connection_url = url self.connection_headers = headers @@ -127,12 +135,13 @@ class Client(base_client.BaseClient): self.socketio_path = socketio_path if namespaces is None: - namespaces = list(set(self.handlers.keys()).union( - set(self.namespace_handlers.keys()))) - if '*' in namespaces: - namespaces.remove('*') + namespaces = list( + set(self.handlers.keys()).union(set(self.namespace_handlers.keys())) + ) + if "*" in namespaces: + namespaces.remove("*") if len(namespaces) == 0: - namespaces = ['/'] + namespaces = ["/"] elif isinstance(namespaces, str): namespaces = [namespaces] self.connection_namespaces = namespaces @@ -144,17 +153,22 @@ class Client(base_client.BaseClient): real_url = self._get_real_value(self.connection_url) real_headers = self._get_real_value(self.connection_headers) try: - self.eio.connect(real_url, headers=real_headers, - transports=transports, - engineio_path=socketio_path) + self.eio.connect( + real_url, + headers=real_headers, + transports=transports, + engineio_path=socketio_path, + ) except engineio.exceptions.ConnectionError as exc: for n in self.connection_namespaces: self._trigger_event( - 'connect_error', n, - exc.args[1] if len(exc.args) > 1 else exc.args[0]) + "connect_error", + n, + exc.args[1] if len(exc.args) > 1 else exc.args[0], + ) if retry: # pragma: no cover self._handle_reconnect() - if self.eio.state == 'connected': + if self.eio.state == "connected": return raise exceptions.ConnectionError(exc.args[0]) from exc @@ -166,7 +180,8 @@ class Client(base_client.BaseClient): 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" + ) self.connected = True @@ -180,14 +195,13 @@ class Client(base_client.BaseClient): self.eio.wait() self.sleep(1) # give the reconnect task time to start up if not self._reconnect_task: - if self.eio.state == 'connected': # pragma: no cover + if self.eio.state == "connected": # pragma: no cover # connected while sleeping above continue - else: - # the reconnect task gave up - break + # the reconnect task gave up + break self._reconnect_task.join() - if self.eio.state != 'connected': + if self.eio.state != "connected": break def emit(self, event, data=None, namespace=None, callback=None): @@ -214,10 +228,11 @@ class Client(base_client.BaseClient): standard concurrency solutions (such as a Lock object) to prevent this situation. """ - namespace = namespace or '/' + namespace = namespace or "/" if namespace not in self.namespaces: raise exceptions.BadNamespaceError( - namespace + ' is not a connected namespace.') + namespace + " is not a connected namespace." + ) self.logger.info('Emitting event "%s" [%s]', event, namespace) if callback is not None: id = self._generate_ack_id(namespace, callback) @@ -231,8 +246,11 @@ class Client(base_client.BaseClient): data = [data] else: data = [] - self._send_packet(self.packet_class(packet.EVENT, namespace=namespace, - data=[event] + data, id=id)) + self._send_packet( + self.packet_class( + packet.EVENT, namespace=namespace, data=[event] + data, id=id + ) + ) def send(self, data, namespace=None, callback=None): """Send a message to the server. @@ -252,8 +270,7 @@ class Client(base_client.BaseClient): that will be passed to the function are those provided by the server. """ - self.emit('message', data=data, namespace=namespace, - callback=callback) + self.emit("message", data=data, namespace=namespace, callback=callback) def call(self, event, data=None, namespace=None, timeout=60): """Emit a custom event to the server and wait for the response. @@ -291,21 +308,23 @@ class Client(base_client.BaseClient): callback_args.append(args) callback_event.set() - self.emit(event, data=data, namespace=namespace, - callback=event_callback) + self.emit(event, data=data, namespace=namespace, callback=event_callback) if not callback_event.wait(timeout=timeout): raise exceptions.TimeoutError() - return callback_args[0] if len(callback_args[0]) > 1 \ - else callback_args[0][0] if len(callback_args[0]) == 1 \ + return ( + callback_args[0] + if len(callback_args[0]) > 1 + else callback_args[0][0] + if len(callback_args[0]) == 1 else None + ) def disconnect(self): """Disconnect from the server.""" # here we just request the disconnection # later in _handle_eio_disconnect we invoke the disconnect handler for n in self.namespaces: - self._send_packet(self.packet_class( - packet.DISCONNECT, namespace=n)) + self._send_packet(self.packet_class(packet.DISCONNECT, namespace=n)) self.eio.disconnect() def shutdown(self): @@ -351,7 +370,8 @@ class Client(base_client.BaseClient): def _get_real_value(self, value): """Return the actual value, for parameters that can also be given as - callables.""" + callables. + """ if not callable(value): return value return value() @@ -366,20 +386,19 @@ class Client(base_client.BaseClient): self.eio.send(encoded_packet) def _handle_connect(self, namespace, data): - namespace = namespace or '/' + namespace = namespace or "/" if namespace not in self.namespaces: - self.logger.info(f'Namespace {namespace} is connected') - self.namespaces[namespace] = (data or {}).get('sid', self.sid) - self._trigger_event('connect', namespace=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() def _handle_disconnect(self, namespace): if not self.connected: return - namespace = namespace or '/' - self._trigger_event('disconnect', namespace, - self.reason.SERVER_DISCONNECT) - self._trigger_event('__disconnect_final', namespace) + namespace = namespace or "/" + 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: @@ -387,7 +406,7 @@ class Client(base_client.BaseClient): self.eio.disconnect(abort=True) def _handle_event(self, namespace, id, data): - namespace = namespace or '/' + namespace = namespace or "/" self.logger.info('Received event "%s" [%s]', data[0], namespace) r = self._trigger_event(data[0], namespace, *data[1:]) if id is not None: @@ -399,36 +418,36 @@ class Client(base_client.BaseClient): data = list(r) else: data = [r] - self._send_packet(self.packet_class( - packet.ACK, namespace=namespace, id=id, data=data)) + self._send_packet( + self.packet_class(packet.ACK, namespace=namespace, id=id, data=data) + ) def _handle_ack(self, namespace, id, data): - namespace = namespace or '/' - self.logger.info('Received ack [%s]', namespace) + namespace = namespace or "/" + self.logger.info("Received ack [%s]", namespace) callback = None try: callback = self.callbacks[namespace][id] except KeyError: # if we get an unknown callback we just ignore it - self.logger.warning('Unknown callback received, ignoring.') + self.logger.warning("Unknown callback received, ignoring.") else: del self.callbacks[namespace][id] if callback is not None: callback(*data) def _handle_error(self, namespace, data): - namespace = namespace or '/' - self.logger.info('Connection to namespace {} was rejected'.format( - namespace)) + namespace = namespace or "/" + self.logger.info(f"Connection to namespace {namespace} was rejected") if data is None: data = tuple() elif not isinstance(data, (tuple, list)): data = (data,) - self._trigger_event('connect_error', namespace, *data) + self._trigger_event("connect_error", namespace, *data) self._connect_event.set() if namespace in self.namespaces: del self.namespaces[namespace] - if namespace == '/': + if namespace == "/": self.namespaces = {} self.connected = False @@ -441,10 +460,10 @@ class Client(base_client.BaseClient): return handler(*args) except TypeError: # the legacy disconnect event does not take a reason argument - if event == 'disconnect': + if event == "disconnect": return handler(*args[:-1]) - else: # pragma: no cover - raise + # pragma: no cover + raise # or else, forward the event to a namespace handler if one exists handler, args = self._get_namespace_handler(namespace, args) @@ -464,46 +483,48 @@ class Client(base_client.BaseClient): if delay > self.reconnection_delay_max: delay = self.reconnection_delay_max delay += self.randomization_factor * (2 * random.random() - 1) - self.logger.info( - 'Connection failed, new attempt in {:.02f} seconds'.format( - delay)) + self.logger.info(f"Connection failed, new attempt in {delay:.02f} seconds") if self._reconnect_abort.wait(delay): - self.logger.info('Reconnect task aborted') + self.logger.info("Reconnect task aborted") for n in self.connection_namespaces: - self._trigger_event('__disconnect_final', namespace=n) + self._trigger_event("__disconnect_final", namespace=n) break attempt_count += 1 try: - self.connect(self.connection_url, - headers=self.connection_headers, - auth=self.connection_auth, - transports=self.connection_transports, - namespaces=self.connection_namespaces, - socketio_path=self.socketio_path, - retry=False) + self.connect( + self.connection_url, + headers=self.connection_headers, + auth=self.connection_auth, + transports=self.connection_transports, + namespaces=self.connection_namespaces, + socketio_path=self.socketio_path, + retry=False, + ) except (exceptions.ConnectionError, ValueError): pass else: - self.logger.info('Reconnection successful') + self.logger.info("Reconnection successful") self._reconnect_task = None break - if self.reconnection_attempts and \ - attempt_count >= self.reconnection_attempts: - self.logger.info( - 'Maximum reconnection attempts reached, giving up') + if ( + self.reconnection_attempts + and attempt_count >= self.reconnection_attempts + ): + self.logger.info("Maximum reconnection attempts reached, giving up") for n in self.connection_namespaces: - self._trigger_event('__disconnect_final', namespace=n) + self._trigger_event("__disconnect_final", namespace=n) break base_client.reconnecting_clients.remove(self) def _handle_eio_connect(self): """Handle the Engine.IO connection event.""" - self.logger.info('Engine.IO connection established') + self.logger.info("Engine.IO connection established") self.sid = self.eio.sid real_auth = self._get_real_value(self.connection_auth) or {} for n in self.connection_namespaces: - self._send_packet(self.packet_class( - packet.CONNECT, data=real_auth, namespace=n)) + self._send_packet( + self.packet_class(packet.CONNECT, data=real_auth, namespace=n) + ) def _handle_eio_message(self, data): """Dispatch Engine.IO messages.""" @@ -525,31 +546,32 @@ class Client(base_client.BaseClient): self._handle_event(pkt.namespace, pkt.id, pkt.data) elif pkt.packet_type == packet.ACK: self._handle_ack(pkt.namespace, pkt.id, pkt.data) - elif pkt.packet_type == packet.BINARY_EVENT or \ - pkt.packet_type == packet.BINARY_ACK: + elif ( + pkt.packet_type == packet.BINARY_EVENT + or pkt.packet_type == packet.BINARY_ACK + ): self._binary_packet = pkt elif pkt.packet_type == packet.CONNECT_ERROR: self._handle_error(pkt.namespace, pkt.data) else: - raise ValueError('Unknown packet type.') + raise ValueError("Unknown packet type.") 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' + 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', n, reason) + self._trigger_event("disconnect", n, reason) if not will_reconnect: - self._trigger_event('__disconnect_final', n) + self._trigger_event("__disconnect_final", n) self.namespaces = {} self.connected = False self.callbacks = {} self._binary_packet = None self.sid = None if will_reconnect and not self._reconnect_task: - self._reconnect_task = self.start_background_task( - self._handle_reconnect) + self._reconnect_task = self.start_background_task(self._handle_reconnect) def _engineio_client_class(self): return engineio.Client diff --git a/src/socketio/exceptions.py b/src/socketio/exceptions.py index 19d6e39..475e74a 100644 --- a/src/socketio/exceptions.py +++ b/src/socketio/exceptions.py @@ -13,17 +13,18 @@ class ConnectionRefusedError(ConnectionError): is not accepted. The positional arguments provided with the exception are returned with the error packet to the client. """ + def __init__(self, *args): if len(args) == 0: - self.error_args = {'message': 'Connection rejected by server'} + self.error_args = {"message": "Connection rejected by server"} elif len(args) == 1: - self.error_args = {'message': str(args[0])} + self.error_args = {"message": str(args[0])} else: - self.error_args = {'message': str(args[0])} + self.error_args = {"message": str(args[0])} if len(args) == 2: - self.error_args['data'] = args[1] + self.error_args["data"] = args[1] else: - self.error_args['data'] = args[1:] + self.error_args["data"] = args[1:] class TimeoutError(SocketIOError): diff --git a/src/socketio/kafka_manager.py b/src/socketio/kafka_manager.py index 11b87ad..0dc7ad0 100644 --- a/src/socketio/kafka_manager.py +++ b/src/socketio/kafka_manager.py @@ -8,7 +8,7 @@ except ImportError: from .pubsub_manager import PubSubManager -logger = logging.getLogger('socketio') +logger = logging.getLogger("socketio") class KafkaManager(PubSubManager): # pragma: no cover @@ -20,7 +20,7 @@ class KafkaManager(PubSubManager): # pragma: no cover To use a Kafka backend, initialize the :class:`Server` instance as follows:: - url = 'kafka://hostname:port' + url = "kafka://hostname:port" server = socketio.Server(client_manager=socketio.KafkaManager(url)) :param url: The connection URL for the Kafka server. For a default Kafka @@ -34,23 +34,29 @@ class KafkaManager(PubSubManager): # pragma: no cover default of ``False`` initializes the class for emitting and receiving. """ - name = 'kafka' - def __init__(self, url='kafka://localhost:9092', channel='socketio', - write_only=False): + name = "kafka" + + def __init__( + self, url="kafka://localhost:9092", channel="socketio", write_only=False + ): if kafka is None: - raise RuntimeError('kafka-python package is not installed ' - '(Run "pip install kafka-python" in your ' - 'virtualenv).') + raise RuntimeError( + "kafka-python package is not installed " + '(Run "pip install kafka-python" in your ' + "virtualenv)." + ) super().__init__(channel=channel, write_only=write_only) urls = [url] if isinstance(url, str) else url - self.kafka_urls = [url[8:] if url != 'kafka://' else 'localhost:9092' - for url in urls] + self.kafka_urls = [ + url[8:] if url != "kafka://" else "localhost:9092" for url in urls + ] self.producer = kafka.KafkaProducer(bootstrap_servers=self.kafka_urls) - self.consumer = kafka.KafkaConsumer(self.channel, - bootstrap_servers=self.kafka_urls) + self.consumer = kafka.KafkaConsumer( + self.channel, bootstrap_servers=self.kafka_urls + ) def _publish(self, data): self.producer.send(self.channel, value=pickle.dumps(data)) diff --git a/src/socketio/kombu_manager.py b/src/socketio/kombu_manager.py index 09e260c..4224f2e 100644 --- a/src/socketio/kombu_manager.py +++ b/src/socketio/kombu_manager.py @@ -44,16 +44,26 @@ class KombuManager(PubSubManager): # pragma: no cover :param producer_options: additional keyword arguments to be passed to ``kombu.Producer()``. """ - name = 'kombu' - def __init__(self, url='amqp://guest:guest@localhost:5672//', - channel='socketio', write_only=False, logger=None, - connection_options=None, exchange_options=None, - queue_options=None, producer_options=None): + name = "kombu" + + def __init__( + self, + url="amqp://guest:guest@localhost:5672//", + channel="socketio", + write_only=False, + logger=None, + connection_options=None, + exchange_options=None, + queue_options=None, + producer_options=None, + ): if kombu is None: - raise RuntimeError('Kombu package is not installed ' - '(Run "pip install kombu" in your ' - 'virtualenv).') + raise RuntimeError( + "Kombu package is not installed " + '(Run "pip install kombu" in your ' + "virtualenv)." + ) super().__init__(channel=channel, write_only=write_only, logger=logger) self.url = url self.connection_options = connection_options or {} @@ -66,52 +76,55 @@ class KombuManager(PubSubManager): # pragma: no cover super().initialize() monkey_patched = True - if self.server.async_mode == 'eventlet': + if self.server.async_mode == "eventlet": from eventlet.patcher import is_monkey_patched - monkey_patched = is_monkey_patched('socket') - elif 'gevent' in self.server.async_mode: + + monkey_patched = is_monkey_patched("socket") + elif "gevent" in self.server.async_mode: from gevent.monkey import is_module_patched - monkey_patched = is_module_patched('socket') + + monkey_patched = is_module_patched("socket") if not monkey_patched: raise RuntimeError( - 'Kombu requires a monkey patched socket library to work ' - 'with ' + self.server.async_mode) + "Kombu requires a monkey patched socket library to work " + "with " + self.server.async_mode + ) def _connection(self): return kombu.Connection(self.url, **self.connection_options) def _exchange(self): - options = {'type': 'fanout', 'durable': False} + options = {"type": "fanout", "durable": False} options.update(self.exchange_options) return kombu.Exchange(self.channel, **options) def _queue(self): - queue_name = 'python-socketio.' + str(uuid.uuid4()) - options = {'durable': False, 'queue_arguments': {'x-expires': 300000}} + 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) def _producer_publish(self, connection): - producer = connection.Producer(exchange=self._exchange(), - **self.producer_options) + producer = connection.Producer( + exchange=self._exchange(), **self.producer_options + ) return connection.ensure(producer, producer.publish) def _publish(self, data): retry = True while True: try: - producer_publish = self._producer_publish( - self.publisher_connection) + producer_publish = self._producer_publish(self.publisher_connection) producer_publish(pickle.dumps(data)) break except (OSError, kombu.exceptions.KombuError): if retry: - self._get_logger().error('Cannot publish to rabbitmq... ' - 'retrying') + self._get_logger().error( + "Cannot publish to rabbitmq... " "retrying" + ) retry = False else: - self._get_logger().error( - 'Cannot publish to rabbitmq... giving up') + self._get_logger().error("Cannot publish to rabbitmq... giving up") break def _listen(self): @@ -128,7 +141,7 @@ class KombuManager(PubSubManager): # pragma: no cover retry_sleep = 1 except (OSError, kombu.exceptions.KombuError): self._get_logger().error( - 'Cannot receive from rabbitmq... ' - 'retrying in {} secs'.format(retry_sleep)) + "Cannot receive from rabbitmq... " f"retrying in {retry_sleep} secs" + ) time.sleep(retry_sleep) retry_sleep = min(retry_sleep * 2, 60) diff --git a/src/socketio/manager.py b/src/socketio/manager.py index 3ebf676..c2132c1 100644 --- a/src/socketio/manager.py +++ b/src/socketio/manager.py @@ -1,10 +1,10 @@ import logging from engineio import packet as eio_packet -from . import base_manager -from . import packet -default_logger = logging.getLogger('socketio') +from . import base_manager, packet + +default_logger = logging.getLogger("socketio") class Manager(base_manager.BaseManager): @@ -16,13 +16,24 @@ class Manager(base_manager.BaseManager): services. More sophisticated storage backends can be implemented by subclasses. """ + def can_disconnect(self, sid, namespace): return self.is_connected(sid, namespace) - def emit(self, event, data, namespace, room=None, skip_sid=None, - callback=None, to=None, **kwargs): + def emit( + self, + event, + data, + namespace, + room=None, + skip_sid=None, + callback=None, + to=None, + **kwargs, + ): """Emit a message to a single client, a room, or all the clients - connected to the namespace.""" + connected to the namespace. + """ room = to or room if namespace not in self.rooms: return @@ -40,12 +51,12 @@ class Manager(base_manager.BaseManager): # when callbacks aren't used the packets sent to each recipient are # identical, so they can be generated once and reused pkt = self.server.packet_class( - packet.EVENT, namespace=namespace, data=[event] + data) + packet.EVENT, namespace=namespace, data=[event] + data + ) encoded_packet = pkt.encode() if not isinstance(encoded_packet, list): encoded_packet = [encoded_packet] - eio_pkt = [eio_packet.Packet(eio_packet.MESSAGE, p) - for p in encoded_packet] + eio_pkt = [eio_packet.Packet(eio_packet.MESSAGE, p) for p in encoded_packet] for sid, eio_sid in self.get_participants(namespace, room): if sid not in skip_sid: for p in eio_pkt: @@ -59,8 +70,8 @@ class Manager(base_manager.BaseManager): if sid not in skip_sid: # pragma: no branch id = self._generate_ack_id(sid, callback) pkt = self.server.packet_class( - packet.EVENT, namespace=namespace, data=[event] + data, - id=id) + packet.EVENT, namespace=namespace, data=[event] + data, id=id + ) self.server._send_packet(eio_sid, pkt) def disconnect(self, sid, namespace, **kwargs): @@ -86,7 +97,7 @@ class Manager(base_manager.BaseManager): callback = self.callbacks[sid][id] except KeyError: # if we get an unknown callback we just ignore it - self._get_logger().warning('Unknown callback received, ignoring.') + self._get_logger().warning("Unknown callback received, ignoring.") else: del self.callbacks[sid][id] if callback is not None: diff --git a/src/socketio/middleware.py b/src/socketio/middleware.py index acc8ffd..c24afe1 100644 --- a/src/socketio/middleware.py +++ b/src/socketio/middleware.py @@ -25,16 +25,22 @@ class WSGIApp(engineio.WSGIApp): sio = socketio.Server() app = socketio.WSGIApp(sio, wsgi_app) - eventlet.wsgi.server(eventlet.listen(('', 8000)), app) + eventlet.wsgi.server(eventlet.listen(("", 8000)), app) """ - def __init__(self, socketio_app, wsgi_app=None, static_files=None, - socketio_path='socket.io'): - super().__init__(socketio_app, wsgi_app, static_files=static_files, - engineio_path=socketio_path) + + def __init__( + self, socketio_app, wsgi_app=None, static_files=None, socketio_path="socket.io" + ): + super().__init__( + socketio_app, + wsgi_app, + static_files=static_files, + engineio_path=socketio_path, + ) class Middleware(WSGIApp): """This class has been renamed to WSGIApp and is now deprecated.""" - def __init__(self, socketio_app, wsgi_app=None, - socketio_path='socket.io'): + + def __init__(self, socketio_app, wsgi_app=None, socketio_path="socket.io"): super().__init__(socketio_app, wsgi_app, socketio_path=socketio_path) diff --git a/src/socketio/msgpack_packet.py b/src/socketio/msgpack_packet.py index 2746263..e3f08c9 100644 --- a/src/socketio/msgpack_packet.py +++ b/src/socketio/msgpack_packet.py @@ -1,4 +1,5 @@ import msgpack + from . import packet @@ -12,7 +13,7 @@ class MsgPackPacket(packet.Packet): def decode(self, encoded_packet): """Decode a transmitted package.""" decoded = msgpack.loads(encoded_packet) - self.packet_type = decoded['type'] - self.data = decoded.get('data') - self.id = decoded.get('id') - self.namespace = decoded['nsp'] + self.packet_type = decoded["type"] + self.data = decoded.get("data") + self.id = decoded.get("id") + self.namespace = decoded["nsp"] diff --git a/src/socketio/namespace.py b/src/socketio/namespace.py index 60cab78..c531516 100644 --- a/src/socketio/namespace.py +++ b/src/socketio/namespace.py @@ -13,6 +13,7 @@ class Namespace(base_namespace.BaseServerNamespace): handlers defined in this class. If this argument is omitted, the default namespace is used. """ + def trigger_event(self, event, *args): """Dispatch an event to the proper handler method. @@ -21,53 +22,96 @@ class Namespace(base_namespace.BaseServerNamespace): method can be overridden if special dispatching rules are needed, or if having a single method that catches all events is desired. """ - handler_name = 'on_' + (event or '') + handler_name = "on_" + (event or "") if hasattr(self, handler_name): try: return getattr(self, handler_name)(*args) except TypeError: # legacy disconnect events do not have a reason argument - if event == 'disconnect': + 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): + # pragma: no cover + raise + + def emit( + self, + event, + data=None, + to=None, + room=None, + skip_sid=None, + namespace=None, + callback=None, + ignore_queue=False, + ): """Emit a custom event to one or more connected clients. The only difference with the :func:`socketio.Server.emit` method is that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.server.emit(event, data=data, to=to, room=room, - skip_sid=skip_sid, - namespace=namespace or self.namespace, - callback=callback, ignore_queue=ignore_queue) - - def send(self, data, to=None, room=None, skip_sid=None, namespace=None, - callback=None, ignore_queue=False): + return self.server.emit( + event, + data=data, + to=to, + room=room, + skip_sid=skip_sid, + namespace=namespace or self.namespace, + callback=callback, + ignore_queue=ignore_queue, + ) + + def send( + self, + data, + to=None, + room=None, + skip_sid=None, + namespace=None, + callback=None, + ignore_queue=False, + ): """Send a message to one or more connected clients. The only difference with the :func:`socketio.Server.send` method is that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.server.send(data, to=to, room=room, skip_sid=skip_sid, - namespace=namespace or self.namespace, - callback=callback, ignore_queue=ignore_queue) - - def call(self, event, data=None, to=None, sid=None, namespace=None, - timeout=None, ignore_queue=False): + return self.server.send( + data, + to=to, + room=room, + skip_sid=skip_sid, + namespace=namespace or self.namespace, + callback=callback, + ignore_queue=ignore_queue, + ) + + def call( + self, + event, + data=None, + to=None, + sid=None, + namespace=None, + timeout=None, + ignore_queue=False, + ): """Emit a custom event to a client and wait for the response. The only difference with the :func:`socketio.Server.call` method is that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.server.call(event, data=data, to=to, sid=sid, - namespace=namespace or self.namespace, - timeout=timeout, ignore_queue=ignore_queue) + return self.server.call( + event, + data=data, + to=to, + sid=sid, + namespace=namespace or self.namespace, + timeout=timeout, + ignore_queue=ignore_queue, + ) def enter_room(self, sid, room, namespace=None): """Enter a room. @@ -76,8 +120,7 @@ class Namespace(base_namespace.BaseServerNamespace): is that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.server.enter_room(sid, room, - namespace=namespace or self.namespace) + return self.server.enter_room(sid, room, namespace=namespace or self.namespace) def leave_room(self, sid, room, namespace=None): """Leave a room. @@ -86,8 +129,7 @@ class Namespace(base_namespace.BaseServerNamespace): is that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.server.leave_room(sid, room, - namespace=namespace or self.namespace) + return self.server.leave_room(sid, room, namespace=namespace or self.namespace) def close_room(self, room, namespace=None): """Close a room. @@ -96,8 +138,7 @@ class Namespace(base_namespace.BaseServerNamespace): is that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.server.close_room(room, - namespace=namespace or self.namespace) + return self.server.close_room(room, namespace=namespace or self.namespace) def get_session(self, sid, namespace=None): """Return the user session for a client. @@ -106,8 +147,7 @@ class Namespace(base_namespace.BaseServerNamespace): method is that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.server.get_session( - sid, namespace=namespace or self.namespace) + return self.server.get_session(sid, namespace=namespace or self.namespace) def save_session(self, sid, session, namespace=None): """Store the user session for a client. @@ -117,7 +157,8 @@ class Namespace(base_namespace.BaseServerNamespace): namespace associated with the class is used. """ return self.server.save_session( - sid, session, namespace=namespace or self.namespace) + sid, session, namespace=namespace or self.namespace + ) def session(self, sid, namespace=None): """Return the user session for a client with context manager syntax. @@ -135,8 +176,7 @@ class Namespace(base_namespace.BaseServerNamespace): is that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.server.disconnect(sid, - namespace=namespace or self.namespace) + return self.server.disconnect(sid, namespace=namespace or self.namespace) class ClientNamespace(base_namespace.BaseClientNamespace): @@ -151,6 +191,7 @@ class ClientNamespace(base_namespace.BaseClientNamespace): handlers defined in this class. If this argument is omitted, the default namespace is used. """ + def trigger_event(self, event, *args): """Dispatch an event to the proper handler method. @@ -159,16 +200,16 @@ class ClientNamespace(base_namespace.BaseClientNamespace): method can be overridden if special dispatching rules are needed, or if having a single method that catches all events is desired. """ - handler_name = 'on_' + (event or '') + handler_name = "on_" + (event or "") if hasattr(self, handler_name): try: return getattr(self, handler_name)(*args) except TypeError: # legacy disconnect events do not have a reason argument - if event == 'disconnect': + if event == "disconnect": return getattr(self, handler_name)(*args[:-1]) - else: # pragma: no cover - raise + # pragma: no cover + raise def emit(self, event, data=None, namespace=None, callback=None): """Emit a custom event to the server. @@ -177,9 +218,9 @@ class ClientNamespace(base_namespace.BaseClientNamespace): that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.client.emit(event, data=data, - namespace=namespace or self.namespace, - callback=callback) + return self.client.emit( + event, data=data, namespace=namespace or self.namespace, callback=callback + ) def send(self, data, room=None, namespace=None, callback=None): """Send a message to the server. @@ -188,8 +229,9 @@ class ClientNamespace(base_namespace.BaseClientNamespace): that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.client.send(data, namespace=namespace or self.namespace, - callback=callback) + return self.client.send( + data, namespace=namespace or self.namespace, callback=callback + ) def call(self, event, data=None, namespace=None, timeout=None): """Emit a custom event to the server and wait for the response. @@ -198,9 +240,9 @@ class ClientNamespace(base_namespace.BaseClientNamespace): that when the ``namespace`` argument is not given the namespace associated with the class is used. """ - return self.client.call(event, data=data, - namespace=namespace or self.namespace, - timeout=timeout) + return self.client.call( + event, data=data, namespace=namespace or self.namespace, timeout=timeout + ) def disconnect(self): """Disconnect from the server. diff --git a/src/socketio/packet.py b/src/socketio/packet.py index f7ad87e..9c26ded 100644 --- a/src/socketio/packet.py +++ b/src/socketio/packet.py @@ -1,10 +1,25 @@ import functools + from engineio import json as _json -(CONNECT, DISCONNECT, EVENT, ACK, CONNECT_ERROR, BINARY_EVENT, BINARY_ACK) = \ - (0, 1, 2, 3, 4, 5, 6) -packet_names = ['CONNECT', 'DISCONNECT', 'EVENT', 'ACK', 'CONNECT_ERROR', - 'BINARY_EVENT', 'BINARY_ACK'] +(CONNECT, DISCONNECT, EVENT, ACK, CONNECT_ERROR, BINARY_EVENT, BINARY_ACK) = ( + 0, + 1, + 2, + 3, + 4, + 5, + 6, +) +packet_names = [ + "CONNECT", + "DISCONNECT", + "EVENT", + "ACK", + "CONNECT_ERROR", + "BINARY_EVENT", + "BINARY_ACK", +] class Packet: @@ -22,21 +37,28 @@ class Packet: uses_binary_events = True json = _json - def __init__(self, packet_type=EVENT, data=None, namespace=None, id=None, - binary=None, encoded_packet=None): + def __init__( + self, + packet_type=EVENT, + data=None, + namespace=None, + id=None, + binary=None, + encoded_packet=None, + ): self.packet_type = packet_type self.data = data self.namespace = namespace self.id = id - if self.uses_binary_events and \ - (binary or (binary is None and self._data_is_binary( - self.data))): + if self.uses_binary_events and ( + binary or (binary is None and self._data_is_binary(self.data)) + ): if self.packet_type == EVENT: self.packet_type = BINARY_EVENT elif self.packet_type == ACK: self.packet_type = BINARY_ACK else: - raise ValueError('Packet does not support binary payload.') + raise ValueError("Packet does not support binary payload.") self.attachment_count = 0 self.attachments = [] if encoded_packet: @@ -52,16 +74,16 @@ class Packet: 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) - encoded_packet += str(len(attachments)) + '-' + encoded_packet += str(len(attachments)) + "-" else: data = self.data attachments = None - if self.namespace is not None and self.namespace != '/': - encoded_packet += self.namespace + ',' + if self.namespace is not None and self.namespace != "/": + encoded_packet += self.namespace + "," if self.id is not None: encoded_packet += str(self.id) if data is not None: - encoded_packet += self.json.dumps(data, separators=(',', ':')) + encoded_packet += self.json.dumps(data, separators=(",", ":")) if attachments is not None: encoded_packet = [encoded_packet] + attachments return encoded_packet @@ -77,26 +99,26 @@ class Packet: self.packet_type = int(ep[0:1]) except TypeError: self.packet_type = ep - ep = '' + ep = "" self.namespace = None self.data = None ep = ep[1:] - dash = ep.find('-') + dash = ep.find("-") attachment_count = 0 if dash > 0 and ep[0:dash].isdigit(): if dash > 10: - raise ValueError('too many attachments') + raise ValueError("too many attachments") attachment_count = int(ep[0:dash]) - ep = ep[dash + 1:] - if ep and ep[0:1] == '/': - sep = ep.find(',') + ep = ep[dash + 1 :] + if ep and ep[0:1] == "/": + sep = ep.find(",") if sep == -1: self.namespace = ep - ep = '' + ep = "" else: self.namespace = ep[0:sep] - ep = ep[sep + 1:] - q = self.namespace.find('?') + ep = ep[sep + 1 :] + q = self.namespace.find("?") if q != -1: self.namespace = self.namespace[0:q] if ep and ep[0].isdigit(): @@ -109,14 +131,14 @@ class Packet: self.id = int(ep[:i]) ep = ep[i:] if len(ep) > 0 and ep[0].isdigit(): - raise ValueError('id field is too long') + raise ValueError("id field is too long") if ep: self.data = self.json.loads(ep) return attachment_count def add_attachment(self, attachment): if self.attachment_count <= len(self.attachments): - raise ValueError('Unexpected binary attachment') + raise ValueError("Unexpected binary attachment") self.attachments.append(attachment) if self.attachment_count == len(self.attachments): self.reconstruct_binary(self.attachments) @@ -127,22 +149,21 @@ class Packet: """Reconstruct a decoded packet using the given list of binary attachments. """ - self.data = self._reconstruct_binary_internal(self.data, - self.attachments) + self.data = self._reconstruct_binary_internal(self.data, self.attachments) def _reconstruct_binary_internal(self, data, attachments): if isinstance(data, list): - return [self._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) - for key, value in data.items()} - else: - return data + return [ + self._reconstruct_binary_internal(item, attachments) for item in data + ] + if isinstance(data, dict): + if data.get("_placeholder") and "num" in data: + return attachments[data["num"]] + return { + key: self._reconstruct_binary_internal(value, attachments) + for key, value in data.items() + } + return data def _deconstruct_binary(self, data): """Extract binary components in the packet.""" @@ -153,38 +174,42 @@ class Packet: def _deconstruct_binary_internal(self, 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) - for item in data] - elif isinstance(data, dict): - return {key: self._deconstruct_binary_internal(value, attachments) - for key, value in data.items()} - else: - return data + return {"_placeholder": True, "num": len(attachments) - 1} + if isinstance(data, list): + return [ + self._deconstruct_binary_internal(item, attachments) for item in data + ] + if isinstance(data, dict): + return { + key: self._deconstruct_binary_internal(value, attachments) + for key, value in data.items() + } + return data def _data_is_binary(self, data): """Check if the data contains binary components.""" if isinstance(data, bytes): return True - elif isinstance(data, list): + if isinstance(data, list): return functools.reduce( - lambda a, b: a or b, [self._data_is_binary(item) - for item in data], False) - elif isinstance(data, dict): + lambda a, b: a or b, + [self._data_is_binary(item) for item in data], + False, + ) + if isinstance(data, dict): return functools.reduce( - lambda a, b: a or b, [self._data_is_binary(item) - for item in data.values()], - False) - else: - return False + lambda a, b: a or b, + [self._data_is_binary(item) for item in data.values()], + False, + ) + return False def _to_dict(self): d = { - 'type': self.packet_type, - 'data': self.data, - 'nsp': self.namespace, + "type": self.packet_type, + "data": self.data, + "nsp": self.namespace, } if self.id is not None: - d['id'] = self.id + d["id"] = self.id return d diff --git a/src/socketio/pubsub_manager.py b/src/socketio/pubsub_manager.py index 3270b4c..d6d8c56 100644 --- a/src/socketio/pubsub_manager.py +++ b/src/socketio/pubsub_manager.py @@ -1,8 +1,8 @@ -from functools import partial +import pickle import uuid +from functools import partial from engineio import json -import pickle from .manager import Manager @@ -21,9 +21,10 @@ class PubSubManager(Manager): :param channel: The channel name on which the server sends and receives notifications. """ - name = 'pubsub' - def __init__(self, channel='socketio', write_only=False, logger=None): + name = "pubsub" + + def __init__(self, channel="socketio", write_only=False, logger=None): super().__init__() self.channel = channel self.write_only = write_only @@ -34,10 +35,19 @@ class PubSubManager(Manager): super().initialize() if not self.write_only: self.thread = self.server.start_background_task(self._thread) - self._get_logger().info(self.name + ' backend initialized.') + self._get_logger().info(self.name + " backend initialized.") - def emit(self, event, data, namespace=None, room=None, skip_sid=None, - callback=None, to=None, **kwargs): + def emit( + self, + event, + data, + namespace=None, + room=None, + skip_sid=None, + callback=None, + to=None, + **kwargs, + ): """Emit a message to a single client, a room, or all the clients connected to the namespace. @@ -47,25 +57,37 @@ class PubSubManager(Manager): The parameters are the same as in :meth:`.Server.emit`. """ room = to or room - if kwargs.get('ignore_queue'): + if kwargs.get("ignore_queue"): return super().emit( - event, data, namespace=namespace, room=room, skip_sid=skip_sid, - callback=callback) - namespace = namespace or '/' + event, + data, + namespace=namespace, + room=room, + skip_sid=skip_sid, + callback=callback, + ) + namespace = namespace or "/" if callback is not None: if self.server is None: - raise RuntimeError('Callbacks can only be issued from the ' - 'context of a server.') + raise RuntimeError( + "Callbacks can only be issued from the " "context of a server." + ) if room is None: - raise ValueError('Cannot use callback without a room set.') + raise ValueError("Cannot use callback without a room set.") id = self._generate_ack_id(room, callback) callback = (room, namespace, id) else: callback = None - message = {'method': 'emit', 'event': event, 'data': data, - 'namespace': namespace, 'room': room, - 'skip_sid': skip_sid, 'callback': callback, - 'host_id': self.host_id} + message = { + "method": "emit", + "event": event, + "data": data, + "namespace": namespace, + "room": room, + "skip_sid": skip_sid, + "callback": callback, + "host_id": self.host_id, + } self._handle_emit(message) # handle in this host self._publish(message) # notify other hosts @@ -73,18 +95,25 @@ class PubSubManager(Manager): if self.is_connected(sid, namespace): # client is in this server, so we can disconnect directly return super().can_disconnect(sid, namespace) - else: - # client is in another server, so we post request to the queue - message = {'method': 'disconnect', 'sid': sid, - 'namespace': namespace or '/', 'host_id': self.host_id} - self._handle_disconnect(message) # handle in this host - self._publish(message) # notify other hosts + # client is in another server, so we post request to the queue + message = { + "method": "disconnect", + "sid": sid, + "namespace": namespace or "/", + "host_id": self.host_id, + } + self._handle_disconnect(message) # handle in this host + self._publish(message) # notify other hosts def disconnect(self, sid, namespace=None, **kwargs): - if kwargs.get('ignore_queue'): + if kwargs.get("ignore_queue"): return super().disconnect(sid, namespace=namespace) - message = {'method': 'disconnect', 'sid': sid, - 'namespace': namespace or '/', 'host_id': self.host_id} + message = { + "method": "disconnect", + "sid": sid, + "namespace": namespace or "/", + "host_id": self.host_id, + } self._handle_disconnect(message) # handle in this host self._publish(message) # notify other hosts @@ -92,23 +121,35 @@ class PubSubManager(Manager): if self.is_connected(sid, namespace): # client is in this server, so we can add to the room directly return super().enter_room(sid, namespace, room, eio_sid=eio_sid) - else: - message = {'method': 'enter_room', 'sid': sid, 'room': room, - 'namespace': namespace or '/', 'host_id': self.host_id} - self._publish(message) # notify other hosts + message = { + "method": "enter_room", + "sid": sid, + "room": room, + "namespace": namespace or "/", + "host_id": self.host_id, + } + self._publish(message) # notify other hosts def leave_room(self, sid, namespace, room): if self.is_connected(sid, namespace): # client is in this server, so we can remove from the room directly return super().leave_room(sid, namespace, room) - else: - message = {'method': 'leave_room', 'sid': sid, 'room': room, - 'namespace': namespace or '/', 'host_id': self.host_id} - self._publish(message) # notify other hosts + message = { + "method": "leave_room", + "sid": sid, + "room": room, + "namespace": namespace or "/", + "host_id": self.host_id, + } + self._publish(message) # notify other hosts def close_room(self, room, namespace=None): - message = {'method': 'close_room', 'room': room, - 'namespace': namespace or '/', 'host_id': self.host_id} + message = { + "method": "close_room", + "room": room, + "namespace": namespace or "/", + "host_id": self.host_id, + } self._handle_close_room(message) # handle in this host self._publish(message) # notify other hosts @@ -118,8 +159,9 @@ class PubSubManager(Manager): This method needs to be implemented by the different subclasses that support pub/sub backends. """ - raise NotImplementedError('This method must be implemented in a ' - 'subclass.') # pragma: no cover + raise NotImplementedError( + "This method must be implemented in a " "subclass." + ) # pragma: no cover def _listen(self): """Return the next message published on the Socket.IO channel, @@ -128,31 +170,35 @@ class PubSubManager(Manager): This method needs to be implemented by the different subclasses that support pub/sub backends. """ - raise NotImplementedError('This method must be implemented in a ' - 'subclass.') # pragma: no cover + raise NotImplementedError( + "This method must be implemented in a " "subclass." + ) # pragma: no cover def _handle_emit(self, message): # Events with callbacks are very tricky to handle across hosts # Here in the receiving end we set up a local callback that preserves # the callback host and id from the sender - remote_callback = message.get('callback') - remote_host_id = message.get('host_id') + remote_callback = message.get("callback") + remote_host_id = message.get("host_id") if remote_callback is not None and len(remote_callback) == 3: - callback = partial(self._return_callback, remote_host_id, - *remote_callback) + callback = partial(self._return_callback, remote_host_id, *remote_callback) else: callback = None - super().emit(message['event'], message['data'], - namespace=message.get('namespace'), - room=message.get('room'), - skip_sid=message.get('skip_sid'), callback=callback) + super().emit( + message["event"], + message["data"], + namespace=message.get("namespace"), + room=message.get("room"), + skip_sid=message.get("skip_sid"), + callback=callback, + ) def _handle_callback(self, message): - if self.host_id == message.get('host_id'): + if self.host_id == message.get("host_id"): try: - sid = message['sid'] - id = message['id'] - args = message['args'] + sid = message["sid"] + id = message["id"] + args = message["args"] except KeyError: return self.trigger_callback(sid, id, args) @@ -163,30 +209,38 @@ class PubSubManager(Manager): if host_id == self.host_id: self.trigger_callback(sid, callback_id, args) else: - self._publish({'method': 'callback', 'host_id': host_id, - 'sid': sid, 'namespace': namespace, - 'id': callback_id, 'args': args}) + self._publish( + { + "method": "callback", + "host_id": host_id, + "sid": sid, + "namespace": namespace, + "id": callback_id, + "args": args, + } + ) def _handle_disconnect(self, message): - self.server.disconnect(sid=message.get('sid'), - namespace=message.get('namespace'), - ignore_queue=True) + self.server.disconnect( + sid=message.get("sid"), + namespace=message.get("namespace"), + ignore_queue=True, + ) def _handle_enter_room(self, message): - sid = message.get('sid') - namespace = message.get('namespace') + sid = message.get("sid") + namespace = message.get("namespace") if self.is_connected(sid, namespace): - super().enter_room(sid, namespace, message.get('room')) + super().enter_room(sid, namespace, message.get("room")) def _handle_leave_room(self, message): - sid = message.get('sid') - namespace = message.get('namespace') + sid = message.get("sid") + namespace = message.get("namespace") if self.is_connected(sid, namespace): - super().leave_room(sid, namespace, message.get('room')) + super().leave_room(sid, namespace, message.get("room")) def _handle_close_room(self, message): - super().close_room(room=message.get('room'), - namespace=message.get('namespace')) + super().close_room(room=message.get("room"), namespace=message.get("namespace")) def _thread(self): while True: @@ -206,28 +260,31 @@ class PubSubManager(Manager): data = json.loads(message) except: pass - if data and 'method' in data: - self._get_logger().debug('pubsub message: {}'.format( - data['method'])) + if data and "method" in data: + self._get_logger().debug( + "pubsub message: {}".format(data["method"]) + ) try: - if data['method'] == 'callback': + if data["method"] == "callback": self._handle_callback(data) - elif data.get('host_id') != self.host_id: - if data['method'] == 'emit': + elif data.get("host_id") != self.host_id: + if data["method"] == "emit": self._handle_emit(data) - elif data['method'] == 'disconnect': + elif data["method"] == "disconnect": self._handle_disconnect(data) - elif data['method'] == 'enter_room': + elif data["method"] == "enter_room": self._handle_enter_room(data) - elif data['method'] == 'leave_room': + elif data["method"] == "leave_room": self._handle_leave_room(data) - elif data['method'] == 'close_room': + elif data["method"] == "close_room": self._handle_close_room(data) except Exception: self.server.logger.exception( - 'Handler error in pubsub listening thread') - self.server.logger.error('pubsub listen() exited unexpectedly') + "Handler error in pubsub listening thread" + ) + self.server.logger.error("pubsub listen() exited unexpectedly") break # loop should never exit except in unit tests! except Exception: # pragma: no cover - self.server.logger.exception('Unexpected Error in pubsub ' - 'listening thread') + self.server.logger.exception( + "Unexpected Error in pubsub " "listening thread" + ) diff --git a/src/socketio/redis_manager.py b/src/socketio/redis_manager.py index c4407df..9278112 100644 --- a/src/socketio/redis_manager.py +++ b/src/socketio/redis_manager.py @@ -10,7 +10,7 @@ except ImportError: from .pubsub_manager import PubSubManager -logger = logging.getLogger('socketio') +logger = logging.getLogger("socketio") def parse_redis_sentinel_url(url): @@ -18,23 +18,23 @@ 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': - raise ValueError('Invalid Redis Sentinel 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) + 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 + kwargs["username"] = parsed_url.username if parsed_url.password: - kwargs['password'] = 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] != '': + 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 @@ -50,7 +50,7 @@ class RedisManager(PubSubManager): # pragma: no cover To use a Redis backend, initialize the :class:`Server` instance as follows:: - url = 'redis://hostname:port/0' + url = "redis://hostname:port/0" server = socketio.Server(client_manager=socketio.RedisManager(url)) :param url: The connection URL for the Redis server. For a default Redis @@ -67,14 +67,23 @@ class RedisManager(PubSubManager): # pragma: no cover :param redis_options: additional keyword arguments to be passed to ``Redis.from_url()`` or ``Sentinel()``. """ - name = 'redis' - def __init__(self, url='redis://localhost:6379/0', channel='socketio', - write_only=False, logger=None, redis_options=None): + name = "redis" + + def __init__( + self, + url="redis://localhost:6379/0", + channel="socketio", + write_only=False, + logger=None, + redis_options=None, + ): if redis is None: - raise RuntimeError('Redis package is not installed ' - '(Run "pip install redis" in your ' - 'virtualenv).') + 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 {} @@ -84,24 +93,27 @@ class RedisManager(PubSubManager): # pragma: no cover super().initialize() monkey_patched = True - if self.server.async_mode == 'eventlet': + if self.server.async_mode == "eventlet": from eventlet.patcher import is_monkey_patched - monkey_patched = is_monkey_patched('socket') - elif 'gevent' in self.server.async_mode: + + monkey_patched = is_monkey_patched("socket") + elif "gevent" in self.server.async_mode: from gevent.monkey import is_module_patched - monkey_patched = is_module_patched('socket') + + monkey_patched = is_module_patched("socket") if not monkey_patched: raise RuntimeError( - 'Redis requires a monkey patched socket library to work ' - 'with ' + self.server.async_mode) + "Redis requires a monkey patched socket library to work " + "with " + self.server.async_mode + ) def _redis_connect(self): - if not self.redis_url.startswith('redis+sentinel://'): - 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) + 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) @@ -117,10 +129,10 @@ class RedisManager(PubSubManager): # pragma: no cover return self.redis.publish(self.channel, pickle.dumps(data)) except redis.exceptions.RedisError: if retry: - logger.error('Cannot publish to redis... retrying') + logger.error("Cannot publish to redis... retrying") retry = False else: - logger.error('Cannot publish to redis... giving up') + logger.error("Cannot publish to redis... giving up") break def _redis_listen_with_retries(self): @@ -134,8 +146,9 @@ class RedisManager(PubSubManager): # pragma: no cover retry_sleep = 1 yield from self.pubsub.listen() except redis.exceptions.RedisError: - logger.error('Cannot receive from redis... ' - 'retrying in {} secs'.format(retry_sleep)) + logger.error( + "Cannot receive from redis... " f"retrying in {retry_sleep} secs" + ) connect = True time.sleep(retry_sleep) retry_sleep *= 2 @@ -143,10 +156,13 @@ class RedisManager(PubSubManager): # pragma: no cover retry_sleep = 60 def _listen(self): - channel = self.channel.encode('utf-8') + 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: - yield message['data'] + if ( + message["channel"] == channel + and message["type"] == "message" + and "data" in message + ): + yield message["data"] self.pubsub.unsubscribe(self.channel) diff --git a/src/socketio/server.py b/src/socketio/server.py index 71c702d..0d1ce3b 100644 --- a/src/socketio/server.py +++ b/src/socketio/server.py @@ -2,11 +2,9 @@ import logging import engineio -from . import base_server -from . import exceptions -from . import packet +from . import base_server, exceptions, packet -default_logger = logging.getLogger('socketio.server') +default_logger = logging.getLogger("socketio.server") class Server(base_server.BaseServer): @@ -115,8 +113,18 @@ class Server(base_server.BaseServer): fatal errors are logged even when ``engineio_logger`` is ``False``. """ - def emit(self, event, data=None, to=None, room=None, skip_sid=None, - namespace=None, callback=None, ignore_queue=False): + + def emit( + self, + event, + data=None, + to=None, + room=None, + skip_sid=None, + namespace=None, + callback=None, + ignore_queue=False, + ): """Emit a custom event to one or more connected clients. :param event: The event name. It can be any string. The event names @@ -159,16 +167,31 @@ class Server(base_server.BaseServer): standard concurrency solutions (such as a Lock object) to prevent this situation. """ - namespace = namespace or '/' + namespace = namespace or "/" room = to or room - self.logger.info('emitting event "%s" to %s [%s]', event, - room or 'all', namespace) - self.manager.emit(event, data, namespace, room=room, - skip_sid=skip_sid, callback=callback, - ignore_queue=ignore_queue) - - def send(self, data, to=None, room=None, skip_sid=None, namespace=None, - callback=None, ignore_queue=False): + self.logger.info( + 'emitting event "%s" to %s [%s]', event, room or "all", namespace + ) + self.manager.emit( + event, + data, + namespace, + room=room, + skip_sid=skip_sid, + callback=callback, + ignore_queue=ignore_queue, + ) + + def send( + self, + data, + to=None, + room=None, + skip_sid=None, + namespace=None, + callback=None, + ignore_queue=False, + ): """Send a message to one or more connected clients. This function emits an event with the name ``'message'``. Use @@ -205,12 +228,27 @@ class Server(base_server.BaseServer): to always leave this parameter with its default value of ``False``. """ - self.emit('message', data=data, to=to, room=room, skip_sid=skip_sid, - namespace=namespace, callback=callback, - ignore_queue=ignore_queue) - - def call(self, event, data=None, to=None, sid=None, namespace=None, - timeout=60, ignore_queue=False): + self.emit( + "message", + data=data, + to=to, + room=room, + skip_sid=skip_sid, + namespace=namespace, + callback=callback, + ignore_queue=ignore_queue, + ) + + def call( + self, + event, + data=None, + to=None, + sid=None, + namespace=None, + timeout=60, + ignore_queue=False, + ): """Emit a custom event to a client and wait for the response. This method issues an emit with a callback and waits for the callback @@ -249,10 +287,9 @@ class Server(base_server.BaseServer): situation. """ if to is None and sid is None: - raise ValueError('Cannot use call() to broadcast.') + raise ValueError("Cannot use call() to broadcast.") if not self.async_handlers: - raise RuntimeError( - 'Cannot use call() when async_handlers is False.') + raise RuntimeError("Cannot use call() when async_handlers is False.") callback_event = self.eio.create_event() callback_args = [] @@ -260,13 +297,23 @@ class Server(base_server.BaseServer): callback_args.append(args) callback_event.set() - self.emit(event, data=data, room=to or sid, namespace=namespace, - callback=event_callback, ignore_queue=ignore_queue) + self.emit( + event, + data=data, + room=to or sid, + namespace=namespace, + callback=event_callback, + ignore_queue=ignore_queue, + ) if not callback_event.wait(timeout=timeout): raise exceptions.TimeoutError() - return callback_args[0] if len(callback_args[0]) > 1 \ - else callback_args[0][0] if len(callback_args[0]) == 1 \ + return ( + callback_args[0] + if len(callback_args[0]) > 1 + else callback_args[0][0] + if len(callback_args[0]) == 1 else None + ) def enter_room(self, sid, room, namespace=None): """Enter a room. @@ -280,8 +327,8 @@ class Server(base_server.BaseServer): :param namespace: The Socket.IO namespace for the event. If this argument is omitted the default namespace is used. """ - namespace = namespace or '/' - self.logger.info('%s is entering room %s [%s]', sid, room, namespace) + namespace = namespace or "/" + self.logger.info("%s is entering room %s [%s]", sid, room, namespace) self.manager.enter_room(sid, namespace, room) def leave_room(self, sid, room, namespace=None): @@ -294,8 +341,8 @@ class Server(base_server.BaseServer): :param namespace: The Socket.IO namespace for the event. If this argument is omitted the default namespace is used. """ - namespace = namespace or '/' - self.logger.info('%s is leaving room %s [%s]', sid, room, namespace) + namespace = namespace or "/" + self.logger.info("%s is leaving room %s [%s]", sid, room, namespace) self.manager.leave_room(sid, namespace, room) def close_room(self, room, namespace=None): @@ -307,8 +354,8 @@ class Server(base_server.BaseServer): :param namespace: The Socket.IO namespace for the event. If this argument is omitted the default namespace is used. """ - namespace = namespace or '/' - self.logger.info('room %s is closing [%s]', room, namespace) + namespace = namespace or "/" + self.logger.info("room %s is closing [%s]", room, namespace) self.manager.close_room(room, namespace) def get_session(self, sid, namespace=None): @@ -323,7 +370,7 @@ class Server(base_server.BaseServer): ``save_session()`` is called, or when the ``session`` context manager is used. """ - namespace = namespace or '/' + namespace = namespace or "/" eio_sid = self.manager.eio_sid_from_sid(sid, namespace) eio_session = self.eio.get_session(eio_sid) return eio_session.setdefault(namespace, {}) @@ -336,7 +383,7 @@ class Server(base_server.BaseServer): :param namespace: The Socket.IO namespace. If this argument is omitted the default namespace is used. """ - namespace = namespace or '/' + namespace = namespace or "/" eio_sid = self.manager.eio_sid_from_sid(sid, namespace) eio_session = self.eio.get_session(eio_sid) eio_session[namespace] = session @@ -350,19 +397,21 @@ class Server(base_server.BaseServer): the client. Any changes that are made to this dictionary inside the context manager block are saved back to the session. Example usage:: - @sio.on('connect') + @sio.on("connect") def on_connect(sid, environ): username = authenticate_user(environ) if not username: return False with sio.session(sid) as session: - session['username'] = username + session["username"] = username + - @sio.on('message') + @sio.on("message") def on_message(sid, msg): with sio.session(sid) as session: - print('received message from ', session['username']) + print("received message from ", session["username"]) """ + class _session_context_manager: def __init__(self, server, sid, namespace): self.server = server @@ -371,13 +420,11 @@ class Server(base_server.BaseServer): self.session = None def __enter__(self): - self.session = self.server.get_session(sid, - namespace=namespace) + self.session = self.server.get_session(sid, namespace=namespace) return self.session def __exit__(self, *args): - self.server.save_session(sid, self.session, - namespace=namespace) + self.server.save_session(sid, self.session, namespace=namespace) return _session_context_manager(self, sid, namespace) @@ -393,20 +440,21 @@ class Server(base_server.BaseServer): recommended to always leave this parameter with its default value of ``False``. """ - namespace = namespace or '/' + namespace = namespace or "/" if ignore_queue: delete_it = self.manager.is_connected(sid, namespace) else: delete_it = self.manager.can_disconnect(sid, namespace) if delete_it: - self.logger.info('Disconnecting %s [%s]', sid, namespace) + self.logger.info("Disconnecting %s [%s]", sid, namespace) 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.reason.SERVER_DISCONNECT) - self.manager.disconnect(sid, namespace=namespace, - ignore_queue=True) + self._send_packet( + eio_sid, self.packet_class(packet.DISCONNECT, namespace=namespace) + ) + self._trigger_event( + "disconnect", namespace, sid, self.reason.SERVER_DISCONNECT + ) + self.manager.disconnect(sid, namespace=namespace, ignore_queue=True) def shutdown(self): """Stop Socket.IO background tasks. @@ -414,7 +462,7 @@ class Server(base_server.BaseServer): This method stops all background activity initiated by the Socket.IO server. It must be called before shutting down the web server. """ - self.logger.info('Socket.IO is shutting down') + self.logger.info("Socket.IO is shutting down") self.eio.shutdown() def handle_request(self, environ, start_response): @@ -460,9 +508,15 @@ class Server(base_server.BaseServer): """ return self.eio.sleep(seconds) - def instrument(self, auth=None, mode='development', read_only=False, - server_id=None, namespace='/admin', - server_stats_interval=2): + def instrument( + self, + auth=None, + mode="development", + read_only=False, + server_id=None, + namespace="/admin", + server_stats_interval=2, + ): """Instrument the Socket.IO server for monitoring with the `Socket.IO Admin UI `_. @@ -494,10 +548,16 @@ class Server(base_server.BaseServer): connected admins. """ from .admin import InstrumentedServer + return InstrumentedServer( - self, auth=auth, mode=mode, read_only=read_only, - server_id=server_id, namespace=namespace, - server_stats_interval=server_stats_interval) + self, + auth=auth, + mode=mode, + read_only=read_only, + server_id=server_id, + namespace=namespace, + server_stats_interval=server_stats_interval, + ) def _send_packet(self, eio_sid, pkt): """Send a Socket.IO packet to a client.""" @@ -514,32 +574,44 @@ class Server(base_server.BaseServer): def _handle_connect(self, eio_sid, namespace, data): """Handle a client connection request.""" - namespace = namespace or '/' + namespace = namespace or "/" sid = None - if namespace in self.handlers or namespace in self.namespace_handlers \ - or self.namespaces == '*' or namespace in self.namespaces: + if ( + namespace in self.handlers + or namespace in self.namespace_handlers + or self.namespaces == "*" + or namespace in self.namespaces + ): sid = self.manager.connect(eio_sid, namespace) if sid is None: - self._send_packet(eio_sid, self.packet_class( - packet.CONNECT_ERROR, data='Unable to connect', - namespace=namespace)) + self._send_packet( + eio_sid, + self.packet_class( + packet.CONNECT_ERROR, data="Unable to connect", namespace=namespace + ), + ) return if self.always_connect: - self._send_packet(eio_sid, self.packet_class( - packet.CONNECT, {'sid': sid}, namespace=namespace)) + self._send_packet( + eio_sid, + self.packet_class(packet.CONNECT, {"sid": sid}, namespace=namespace), + ) fail_reason = exceptions.ConnectionRefusedError().error_args try: if data: success = self._trigger_event( - 'connect', namespace, sid, self.environ[eio_sid], data) + "connect", namespace, sid, self.environ[eio_sid], data + ) else: try: success = self._trigger_event( - 'connect', namespace, sid, self.environ[eio_sid]) + "connect", namespace, sid, self.environ[eio_sid] + ) except TypeError: success = self._trigger_event( - 'connect', namespace, sid, self.environ[eio_sid], None) + "connect", namespace, sid, self.environ[eio_sid], None + ) except exceptions.ConnectionRefusedError as exc: fail_reason = exc.error_args success = False @@ -547,47 +619,54 @@ class Server(base_server.BaseServer): if success is False: if self.always_connect: self.manager.pre_disconnect(sid, namespace) - self._send_packet(eio_sid, self.packet_class( - packet.DISCONNECT, data=fail_reason, namespace=namespace)) + self._send_packet( + eio_sid, + self.packet_class( + packet.DISCONNECT, data=fail_reason, namespace=namespace + ), + ) else: - self._send_packet(eio_sid, self.packet_class( - packet.CONNECT_ERROR, data=fail_reason, - namespace=namespace)) + self._send_packet( + eio_sid, + self.packet_class( + packet.CONNECT_ERROR, data=fail_reason, namespace=namespace + ), + ) self.manager.disconnect(sid, namespace, ignore_queue=True) elif not self.always_connect: - self._send_packet(eio_sid, self.packet_class( - packet.CONNECT, {'sid': sid}, namespace=namespace)) + self._send_packet( + eio_sid, + self.packet_class(packet.CONNECT, {"sid": sid}, namespace=namespace), + ) def _handle_disconnect(self, eio_sid, namespace, reason=None): """Handle a client disconnect.""" - namespace = namespace or '/' + 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, - reason or self.reason.CLIENT_DISCONNECT) + 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): """Handle an incoming client event.""" - namespace = namespace or '/' + namespace = namespace or "/" sid = self.manager.sid_from_eio_sid(eio_sid, namespace) - self.logger.info('received event "%s" from %s [%s]', data[0], sid, - namespace) + self.logger.info('received event "%s" from %s [%s]', data[0], sid, namespace) if not self.manager.is_connected(sid, namespace): - self.logger.warning('%s is not connected to namespace %s', - sid, namespace) + self.logger.warning("%s is not connected to namespace %s", sid, namespace) return if self.async_handlers: - self.start_background_task(self._handle_event_internal, self, sid, - eio_sid, data, namespace, id) + self.start_background_task( + self._handle_event_internal, self, sid, eio_sid, data, namespace, id + ) else: - self._handle_event_internal(self, sid, eio_sid, data, namespace, - id) + self._handle_event_internal(self, sid, eio_sid, data, namespace, id) - def _handle_event_internal(self, server, sid, eio_sid, data, namespace, - id): + def _handle_event_internal(self, server, sid, eio_sid, data, namespace, id): r = server._trigger_event(data[0], namespace, sid, *data[1:]) if r != self.not_handled and id is not None: # send ACK packet with the response returned by the handler @@ -598,14 +677,16 @@ class Server(base_server.BaseServer): data = list(r) else: data = [r] - server._send_packet(eio_sid, self.packet_class( - packet.ACK, namespace=namespace, id=id, data=data)) + server._send_packet( + eio_sid, + self.packet_class(packet.ACK, namespace=namespace, id=id, data=data), + ) def _handle_ack(self, eio_sid, namespace, id, data): """Handle ACK packets from the client.""" - namespace = namespace or '/' + namespace = namespace or "/" sid = self.manager.sid_from_eio_sid(eio_sid, namespace) - self.logger.info('received ack from %s [%s]', sid, namespace) + self.logger.info("received ack from %s [%s]", sid, namespace) self.manager.trigger_callback(sid, id, data) def _trigger_event(self, event, namespace, *args): @@ -617,16 +698,15 @@ class Server(base_server.BaseServer): return handler(*args) except TypeError: # legacy disconnect events use only one argument - if event == 'disconnect': + if event == "disconnect": return handler(*args[:-1]) - else: # pragma: no cover - raise + # 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: return handler.trigger_event(event, *args) - else: - return self.not_handled + return self.not_handled def _handle_eio_connect(self, eio_sid, environ): """Handle the Engine.IO connection event.""" @@ -642,8 +722,7 @@ class Server(base_server.BaseServer): if pkt.add_attachment(data): del self._binary_packet[eio_sid] if pkt.packet_type == packet.BINARY_EVENT: - self._handle_event(eio_sid, pkt.namespace, pkt.id, - pkt.data) + self._handle_event(eio_sid, pkt.namespace, pkt.id, pkt.data) else: self._handle_ack(eio_sid, pkt.namespace, pkt.id, pkt.data) else: @@ -651,19 +730,22 @@ class Server(base_server.BaseServer): 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.reason.CLIENT_DISCONNECT) + 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: self._handle_ack(eio_sid, pkt.namespace, pkt.id, pkt.data) - elif pkt.packet_type == packet.BINARY_EVENT or \ - pkt.packet_type == packet.BINARY_ACK: + elif ( + pkt.packet_type == packet.BINARY_EVENT + or pkt.packet_type == packet.BINARY_ACK + ): self._binary_packet[eio_sid] = pkt elif pkt.packet_type == packet.CONNECT_ERROR: - raise ValueError('Unexpected CONNECT_ERROR packet.') + raise ValueError("Unexpected CONNECT_ERROR packet.") else: - raise ValueError('Unknown packet type.') + raise ValueError("Unknown packet type.") def _handle_eio_disconnect(self, eio_sid, reason): """Handle Engine.IO disconnect event.""" diff --git a/src/socketio/simple_client.py b/src/socketio/simple_client.py index 3f046b4..fc5b5c2 100644 --- a/src/socketio/simple_client.py +++ b/src/socketio/simple_client.py @@ -1,6 +1,7 @@ from threading import Event + from socketio import Client -from socketio.exceptions import SocketIOError, TimeoutError, DisconnectedError +from socketio.exceptions import DisconnectedError, SocketIOError, TimeoutError class SimpleClient: @@ -12,20 +13,29 @@ 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 self.client = None - self.namespace = '/' + self.namespace = "/" self.connected_event = Event() self.connected = False self.input_event = Event() self.input_buffer = [] - def connect(self, url, headers={}, auth=None, transports=None, - namespace='/', socketio_path='socket.io', wait_timeout=5): + def connect( + self, + url, + headers={}, + auth=None, + transports=None, + namespace="/", + socketio_path="socket.io", + wait_timeout=5, + ): """Connect to a Socket.IO server. :param url: The URL of the Socket.IO server. It can include custom @@ -56,12 +66,11 @@ class SimpleClient: seconds. """ if self.connected: - raise RuntimeError('Already connected') + raise RuntimeError("Already connected") self.namespace = namespace self.input_buffer = [] self.input_event.clear() - self.client = self.client_class( - *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 @@ -77,15 +86,20 @@ class SimpleClient: self.connected = False self.connected_event.set() - @self.client.on('*', namespace=self.namespace) + @self.client.on("*", namespace=self.namespace) def on_event(event, *args): # pragma: no cover self.input_buffer.append([event, *args]) self.input_event.set() - self.client.connect(url, headers=headers, auth=auth, - transports=transports, namespaces=[namespace], - socketio_path=socketio_path, - wait_timeout=wait_timeout) + self.client.connect( + url, + headers=headers, + auth=auth, + transports=transports, + namespaces=[namespace], + socketio_path=socketio_path, + wait_timeout=wait_timeout, + ) @property def sid(self): @@ -103,7 +117,7 @@ class SimpleClient: 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. @@ -153,8 +167,9 @@ class SimpleClient: if not self.connected: raise DisconnectedError() try: - return self.client.call(event, data, namespace=self.namespace, - timeout=timeout) + return self.client.call( + event, data, namespace=self.namespace, timeout=timeout + ) except SocketIOError: pass @@ -170,8 +185,7 @@ class SimpleClient: additional list elements. """ while not self.input_buffer: - if not self.connected_event.wait( - timeout=timeout): # pragma: no cover + if not self.connected_event.wait(timeout=timeout): # pragma: no cover raise TimeoutError() if not self.connected: raise DisconnectedError() diff --git a/src/socketio/tornado.py b/src/socketio/tornado.py index 160bd32..daf243d 100644 --- a/src/socketio/tornado.py +++ b/src/socketio/tornado.py @@ -1,6 +1,7 @@ try: - from engineio.async_drivers.tornado import get_tornado_handler as \ - get_engineio_handler + from engineio.async_drivers.tornado import ( + get_tornado_handler as get_engineio_handler, + ) except ImportError: # pragma: no cover get_engineio_handler = None diff --git a/src/socketio/zmq_manager.py b/src/socketio/zmq_manager.py index aa5a49a..b993bce 100644 --- a/src/socketio/zmq_manager.py +++ b/src/socketio/zmq_manager.py @@ -14,7 +14,7 @@ class ZmqManager(PubSubManager): # pragma: no cover processes. To use a zmq backend, initialize the :class:`Server` instance as follows:: - url = 'zmq+tcp://hostname:port1+port2' + url = "zmq+tcp://hostname:port1+port2" server = socketio.Server(client_manager=socketio.ZmqManager(url)) :param url: The connection URL for the zmq message broker, @@ -40,34 +40,40 @@ class ZmqManager(PubSubManager): # pragma: no cover while True: publisher.send(receiver.recv()) """ - name = 'zmq' - def __init__(self, url='zmq+tcp://localhost:5555+5556', - channel='socketio', - write_only=False, - logger=None): + name = "zmq" + + def __init__( + self, + url="zmq+tcp://localhost:5555+5556", + channel="socketio", + write_only=False, + logger=None, + ): try: from eventlet.green import zmq except ImportError: - raise RuntimeError('zmq package is not installed ' - '(Run "pip install pyzmq" in your ' - 'virtualenv).') + raise RuntimeError( + "zmq package is not installed " + '(Run "pip install pyzmq" in your ' + "virtualenv)." + ) - r = re.compile(r':\d+\+\d+$') - if not (url.startswith('zmq+tcp://') and r.search(url)): - raise RuntimeError('unexpected connection string: ' + url) + r = re.compile(r":\d+\+\d+$") + 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] + url = url.replace("zmq+", "") + (sink_url, sub_port) = url.split("+") + sink_port = sink_url.split(":")[-1] sub_url = sink_url.replace(sink_port, sub_port) sink = zmq.Context().socket(zmq.PUSH) sink.connect(sink_url) sub = zmq.Context().socket(zmq.SUB) - sub.setsockopt_string(zmq.SUBSCRIBE, '') + sub.setsockopt_string(zmq.SUBSCRIBE, "") sub.connect(sub_url) self.sink = sink @@ -76,11 +82,7 @@ class ZmqManager(PubSubManager): # pragma: no cover def _publish(self, data): pickled_data = pickle.dumps( - { - 'type': 'message', - 'channel': self.channel, - 'data': data - } + {"type": "message", "channel": self.channel, "data": data} ) return self.sink.send(pickled_data) @@ -97,9 +99,11 @@ class ZmqManager(PubSubManager): # pragma: no cover message = pickle.loads(message) except Exception: pass - if isinstance(message, dict) and \ - message['type'] == 'message' and \ - message['channel'] == self.channel and \ - 'data' in message: - yield message['data'] + if ( + isinstance(message, dict) + and message["type"] == "message" + and message["channel"] == self.channel + and "data" in message + ): + yield message["data"] return diff --git a/tests/async/test_admin.py b/tests/async/test_admin.py index a1cf97c..e950acb 100644 --- a/tests/async/test_admin.py +++ b/tests/async/test_admin.py @@ -1,8 +1,10 @@ -from functools import wraps import threading import time +from functools import wraps from unittest import mock + import pytest + try: from engineio.async_socket import AsyncSocket as EngineIOSocket except ImportError: @@ -18,10 +20,11 @@ def with_instrumented_server(auth=False, **ikwargs): Admin UI project. The arguments passed to the decorator are passed directly to the ``instrument()`` method of the server. """ + def decorator(f): @wraps(f) def wrapped(self, *args, **kwargs): - sio = socketio.AsyncServer(async_mode='asgi') + sio = socketio.AsyncServer(async_mode="asgi") @sio.event async def enter_room(sid, data): @@ -31,7 +34,7 @@ def with_instrumented_server(auth=False, **ikwargs): async def emit(sid, event): await sio.emit(event, skip_sid=sid) - @sio.event(namespace='/foo') + @sio.event(namespace="/foo") def connect(sid, environ, auth): pass @@ -39,8 +42,8 @@ def with_instrumented_server(auth=False, **ikwargs): await self.isvr.shutdown() await sio.shutdown() - if 'server_stats_interval' not in ikwargs: - ikwargs['server_stats_interval'] = 0.25 + if "server_stats_interval" not in ikwargs: + ikwargs["server_stats_interval"] = 0.25 self.isvr = sio.instrument(auth=auth, **ikwargs) server = SocketIOWebServer(sio, on_shutdown=shutdown) @@ -67,25 +70,27 @@ def with_instrumented_server(auth=False, **ikwargs): # logging.getLogger('socketio.client').setLevel(logging.NOTSET) return ret + return wrapped + return decorator def _custom_auth(auth): - return auth == {'foo': 'bar'} + return auth == {"foo": "bar"} async def _async_custom_auth(auth): - return auth == {'foo': 'bar'} + return auth == {"foo": "bar"} class TestAsyncAdmin: def setup_method(self): - print('threads at start:', threading.enumerate()) + print("threads at start:", threading.enumerate()) self.thread_count = threading.active_count() def teardown_method(self): - print('threads at end:', threading.enumerate()) + print("threads at end:", threading.enumerate()) assert self.thread_count == threading.active_count() def _expect(self, expected, admin_client): @@ -101,206 +106,221 @@ class TestAsyncAdmin: return events def test_missing_auth(self): - sio = socketio.AsyncServer(async_mode='asgi') + sio = socketio.AsyncServer(async_mode="asgi") with pytest.raises(ValueError): sio.instrument() @with_instrumented_server(auth=False) def test_admin_connect_with_no_auth(self): with socketio.SimpleClient() as admin_client: - admin_client.connect('http://localhost:8900', namespace='/admin') + admin_client.connect("http://localhost:8900", namespace="/admin") with socketio.SimpleClient() as admin_client: - admin_client.connect('http://localhost:8900', namespace='/admin', - auth={'foo': 'bar'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "bar"} + ) - @with_instrumented_server(auth={'foo': 'bar'}) + @with_instrumented_server(auth={"foo": "bar"}) 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'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "bar"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): admin_client.connect( - 'http://localhost:8900', namespace='/admin', - auth={'foo': 'baz'}) + "http://localhost:8900", namespace="/admin", auth={"foo": "baz"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect( - 'http://localhost:8900', namespace='/admin') + admin_client.connect("http://localhost:8900", namespace="/admin") - @with_instrumented_server(auth=[{'foo': 'bar'}, - {'u': 'admin', 'p': 'secret'}]) + @with_instrumented_server(auth=[{"foo": "bar"}, {"u": "admin", "p": "secret"}]) 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'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "bar"} + ) with socketio.SimpleClient() as admin_client: - admin_client.connect('http://localhost:8900', namespace='/admin', - auth={'u': 'admin', 'p': 'secret'}) + admin_client.connect( + "http://localhost:8900", + namespace="/admin", + auth={"u": "admin", "p": "secret"}, + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect('http://localhost:8900', - namespace='/admin', auth={'foo': 'baz'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "baz"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect('http://localhost:8900', - namespace='/admin') + 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: - admin_client.connect('http://localhost:8900', namespace='/admin', - auth={'foo': 'bar'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "bar"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect('http://localhost:8900', - namespace='/admin', auth={'foo': 'baz'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "baz"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect('http://localhost:8900', - namespace='/admin') + 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: - admin_client.connect('http://localhost:8900', namespace='/admin', - auth={'foo': 'bar'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "bar"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect('http://localhost:8900', - namespace='/admin', auth={'foo': 'baz'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "baz"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect('http://localhost:8900', - namespace='/admin') + admin_client.connect("http://localhost:8900", namespace="/admin") @with_instrumented_server() def test_admin_connect_only_admin(self): with socketio.SimpleClient() as admin_client: - admin_client.connect('http://localhost:8900', namespace='/admin') + admin_client.connect("http://localhost:8900", namespace="/admin") sid = admin_client.sid - events = self._expect({'config': 1, 'all_sockets': 1, - 'server_stats': 2}, admin_client) - - assert 'supportedFeatures' in events['config'] - assert 'ALL_EVENTS' in events['config']['supportedFeatures'] - assert 'AGGREGATED_EVENTS' in events['config']['supportedFeatures'] - assert 'EMIT' in events['config']['supportedFeatures'] - assert len(events['all_sockets']) == 1 - assert events['all_sockets'][0]['id'] == sid - assert events['all_sockets'][0]['rooms'] == [sid] - assert events['server_stats']['clientsCount'] == 1 - assert events['server_stats']['pollingClientsCount'] == 0 - assert len(events['server_stats']['namespaces']) == 3 - assert {'name': '/', 'socketsCount': 0} in \ - events['server_stats']['namespaces'] - assert {'name': '/foo', 'socketsCount': 0} in \ - events['server_stats']['namespaces'] - assert {'name': '/admin', 'socketsCount': 1} in \ - events['server_stats']['namespaces'] + events = self._expect( + {"config": 1, "all_sockets": 1, "server_stats": 2}, admin_client + ) + + assert "supportedFeatures" in events["config"] + assert "ALL_EVENTS" in events["config"]["supportedFeatures"] + assert "AGGREGATED_EVENTS" in events["config"]["supportedFeatures"] + assert "EMIT" in events["config"]["supportedFeatures"] + assert len(events["all_sockets"]) == 1 + assert events["all_sockets"][0]["id"] == sid + assert events["all_sockets"][0]["rooms"] == [sid] + assert events["server_stats"]["clientsCount"] == 1 + assert events["server_stats"]["pollingClientsCount"] == 0 + assert len(events["server_stats"]["namespaces"]) == 3 + assert {"name": "/", "socketsCount": 0} in events["server_stats"]["namespaces"] + assert {"name": "/foo", "socketsCount": 0} in events["server_stats"][ + "namespaces" + ] + assert {"name": "/admin", "socketsCount": 1} in events["server_stats"][ + "namespaces" + ] @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: - client1.connect('http://localhost:8900') - client1.emit('enter_room', 'room') + with ( + socketio.SimpleClient() as client1, + socketio.SimpleClient() as client2, + socketio.SimpleClient() as client3, + socketio.SimpleClient() as admin_client, + ): + client1.connect("http://localhost:8900") + client1.emit("enter_room", "room") sid1 = client1.sid saved_check_for_upgrade = self.isvr._check_for_upgrade self.isvr._check_for_upgrade = mock.AsyncMock() - client2.connect('http://localhost:8900', namespace='/foo', - transports=['polling']) + client2.connect( + "http://localhost:8900", namespace="/foo", transports=["polling"] + ) sid2 = client2.sid self.isvr._check_for_upgrade = saved_check_for_upgrade - client3.connect('http://localhost:8900', namespace='/admin') + client3.connect("http://localhost:8900", namespace="/admin") sid3 = client3.sid - admin_client.connect('http://localhost:8900', namespace='/admin') + admin_client.connect("http://localhost:8900", namespace="/admin") sid = admin_client.sid - events = self._expect({'config': 1, 'all_sockets': 1, - 'server_stats': 2}, admin_client) - - assert 'supportedFeatures' in events['config'] - assert 'ALL_EVENTS' in events['config']['supportedFeatures'] - assert 'AGGREGATED_EVENTS' in events['config']['supportedFeatures'] - assert 'EMIT' in events['config']['supportedFeatures'] - assert len(events['all_sockets']) == 4 - assert events['server_stats']['clientsCount'] == 4 - assert events['server_stats']['pollingClientsCount'] == 1 - assert len(events['server_stats']['namespaces']) == 3 - assert {'name': '/', 'socketsCount': 1} in \ - events['server_stats']['namespaces'] - assert {'name': '/foo', 'socketsCount': 1} in \ - events['server_stats']['namespaces'] - assert {'name': '/admin', 'socketsCount': 2} in \ - events['server_stats']['namespaces'] - - for socket in events['all_sockets']: - if socket['id'] == sid: - assert socket['rooms'] == [sid] - elif socket['id'] == sid1: - assert socket['rooms'] == [sid1, 'room'] - elif socket['id'] == sid2: - assert socket['rooms'] == [sid2] - elif socket['id'] == sid3: - assert socket['rooms'] == [sid3] - - @with_instrumented_server(mode='production', read_only=True) + events = self._expect( + {"config": 1, "all_sockets": 1, "server_stats": 2}, admin_client + ) + + assert "supportedFeatures" in events["config"] + assert "ALL_EVENTS" in events["config"]["supportedFeatures"] + assert "AGGREGATED_EVENTS" in events["config"]["supportedFeatures"] + assert "EMIT" in events["config"]["supportedFeatures"] + assert len(events["all_sockets"]) == 4 + assert events["server_stats"]["clientsCount"] == 4 + assert events["server_stats"]["pollingClientsCount"] == 1 + assert len(events["server_stats"]["namespaces"]) == 3 + assert {"name": "/", "socketsCount": 1} in events["server_stats"]["namespaces"] + assert {"name": "/foo", "socketsCount": 1} in events["server_stats"][ + "namespaces" + ] + assert {"name": "/admin", "socketsCount": 2} in events["server_stats"][ + "namespaces" + ] + + for socket in events["all_sockets"]: + if socket["id"] == sid: + assert socket["rooms"] == [sid] + elif socket["id"] == sid1: + assert socket["rooms"] == [sid1, "room"] + elif socket["id"] == sid2: + assert socket["rooms"] == [sid2] + elif socket["id"] == sid3: + assert socket["rooms"] == [sid3] + + @with_instrumented_server(mode="production", read_only=True) 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}, - admin_client) - - assert 'supportedFeatures' in events['config'] - assert 'ALL_EVENTS' not in events['config']['supportedFeatures'] - assert 'AGGREGATED_EVENTS' in events['config']['supportedFeatures'] - assert 'EMIT' not in events['config']['supportedFeatures'] - assert events['server_stats']['clientsCount'] == 1 - assert events['server_stats']['pollingClientsCount'] == 0 - assert len(events['server_stats']['namespaces']) == 3 - assert {'name': '/', 'socketsCount': 0} in \ - events['server_stats']['namespaces'] - assert {'name': '/foo', 'socketsCount': 0} in \ - events['server_stats']['namespaces'] - assert {'name': '/admin', 'socketsCount': 1} in \ - events['server_stats']['namespaces'] + admin_client.connect("http://localhost:8900", namespace="/admin") + events = self._expect({"config": 1, "server_stats": 2}, admin_client) + + assert "supportedFeatures" in events["config"] + assert "ALL_EVENTS" not in events["config"]["supportedFeatures"] + assert "AGGREGATED_EVENTS" in events["config"]["supportedFeatures"] + assert "EMIT" not in events["config"]["supportedFeatures"] + assert events["server_stats"]["clientsCount"] == 1 + assert events["server_stats"]["pollingClientsCount"] == 0 + assert len(events["server_stats"]["namespaces"]) == 3 + assert {"name": "/", "socketsCount": 0} in events["server_stats"]["namespaces"] + assert {"name": "/foo", "socketsCount": 0} in events["server_stats"][ + "namespaces" + ] + assert {"name": "/admin", "socketsCount": 1} in events["server_stats"][ + "namespaces" + ] @with_instrumented_server() def test_admin_features(self): - with socketio.SimpleClient() as client1, \ - socketio.SimpleClient() as client2, \ - socketio.SimpleClient() as admin_client: - client1.connect('http://localhost:8900') - client2.connect('http://localhost:8900') - admin_client.connect('http://localhost:8900', namespace='/admin') + with ( + socketio.SimpleClient() as client1, + socketio.SimpleClient() as client2, + socketio.SimpleClient() as admin_client, + ): + client1.connect("http://localhost:8900") + client2.connect("http://localhost:8900") + admin_client.connect("http://localhost:8900", namespace="/admin") # emit from admin admin_client.emit( - 'emit', ('/', client1.sid, 'foo', {'bar': 'baz'}, 'extra')) + "emit", ("/", client1.sid, "foo", {"bar": "baz"}, "extra") + ) data = client1.receive(timeout=5) - assert data == ['foo', {'bar': 'baz'}, 'extra'] + assert data == ["foo", {"bar": "baz"}, "extra"] # emit from regular client - client1.emit('emit', 'foo') + client1.emit("emit", "foo") data = client2.receive(timeout=5) - assert data == ['foo'] + assert data == ["foo"] # join and leave - admin_client.emit('join', ('/', 'room', client1.sid)) + admin_client.emit("join", ("/", "room", client1.sid)) time.sleep(0.2) - admin_client.emit( - 'emit', ('/', 'room', 'foo', {'bar': 'baz'})) + admin_client.emit("emit", ("/", "room", "foo", {"bar": "baz"})) data = client1.receive(timeout=5) - assert data == ['foo', {'bar': 'baz'}] - admin_client.emit('leave', ('/', 'room')) + assert data == ["foo", {"bar": "baz"}] + admin_client.emit("leave", ("/", "room")) # disconnect - admin_client.emit('_disconnect', ('/', False, client1.sid)) + admin_client.emit("_disconnect", ("/", False, client1.sid)) for _ in range(10): if not client1.connected: break diff --git a/tests/async/test_client.py b/tests/async/test_client.py index d7e0f9e..e25a5f1 100644 --- a/tests/async/test_client.py +++ b/tests/async/test_client.py @@ -2,12 +2,9 @@ import asyncio from unittest import mock import pytest - -from socketio import async_client -from socketio import async_namespace from engineio import exceptions as engineio_exceptions -from socketio import exceptions -from socketio import packet + +from socketio import async_client, async_namespace, exceptions, packet class TestAsyncClient: @@ -19,133 +16,135 @@ class TestAsyncClient: c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() await c.connect( - 'url', - headers='headers', - auth='auth', - transports='transports', - namespaces=['/foo', '/', '/bar'], - socketio_path='path', + "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' - assert c.connection_auth == 'auth' - assert c.connection_transports == 'transports' - assert c.connection_namespaces == ['/foo', '/', '/bar'] - assert c.socketio_path == 'path' + assert c.connection_url == "url" + assert c.connection_headers == "headers" + assert c.connection_auth == "auth" + assert c.connection_transports == "transports" + assert c.connection_namespaces == ["/foo", "/", "/bar"] + assert c.socketio_path == "path" c.eio.connect.assert_awaited_once_with( - 'url', - headers='headers', - transports='transports', - engineio_path='path', + "url", + headers="headers", + transports="transports", + engineio_path="path", ) async def test_connect_functions(self): async def headers(): - return 'headers' + return "headers" c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() await c.connect( - lambda: 'url', + lambda: "url", headers=headers, - auth='auth', - transports='transports', - namespaces=['/foo', '/', '/bar'], - socketio_path='path', + auth="auth", + transports="transports", + namespaces=["/foo", "/", "/bar"], + socketio_path="path", wait=False, ) c.eio.connect.assert_awaited_once_with( - 'url', - headers='headers', - transports='transports', - engineio_path='path', + "url", + headers="headers", + transports="transports", + engineio_path="path", ) async def test_connect_one_namespace(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() await c.connect( - 'url', - headers='headers', - transports='transports', - namespaces='/foo', - socketio_path='path', + "url", + headers="headers", + transports="transports", + namespaces="/foo", + socketio_path="path", wait=False, ) - assert c.connection_url == 'url' - assert c.connection_headers == 'headers' - assert c.connection_transports == 'transports' - assert c.connection_namespaces == ['/foo'] - assert c.socketio_path == 'path' + assert c.connection_url == "url" + assert c.connection_headers == "headers" + assert c.connection_transports == "transports" + assert c.connection_namespaces == ["/foo"] + assert c.socketio_path == "path" c.eio.connect.assert_awaited_once_with( - 'url', - headers='headers', - transports='transports', - engineio_path='path', + "url", + headers="headers", + transports="transports", + engineio_path="path", ) 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='*') + c.on("foo", mock.MagicMock(), namespace="/foo") + c.on("bar", mock.MagicMock(), namespace="/") + c.on("baz", mock.MagicMock(), namespace="*") await c.connect( - 'url', - headers='headers', - transports='transports', - socketio_path='path', + "url", + headers="headers", + transports="transports", + socketio_path="path", wait=False, ) - assert c.connection_url == 'url' - assert c.connection_headers == 'headers' - assert c.connection_transports == 'transports' - assert c.connection_namespaces == ['/', '/foo'] or \ - c.connection_namespaces == ['/foo', '/'] - assert c.socketio_path == 'path' + assert c.connection_url == "url" + assert c.connection_headers == "headers" + assert c.connection_transports == "transports" + assert c.connection_namespaces == ["/", "/foo"] or c.connection_namespaces == [ + "/foo", + "/", + ] + assert c.socketio_path == "path" c.eio.connect.assert_awaited_once_with( - 'url', - headers='headers', - transports='transports', - engineio_path='path', + "url", + headers="headers", + transports="transports", + engineio_path="path", ) async def test_connect_no_namespaces(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() await c.connect( - 'url', - headers='headers', - transports='transports', - socketio_path='path', + "url", + headers="headers", + transports="transports", + socketio_path="path", wait=False, ) - assert c.connection_url == 'url' - assert c.connection_headers == 'headers' - assert c.connection_transports == 'transports' - assert c.connection_namespaces == ['/'] - assert c.socketio_path == 'path' + assert c.connection_url == "url" + assert c.connection_headers == "headers" + assert c.connection_transports == "transports" + assert c.connection_namespaces == ["/"] + assert c.socketio_path == "path" c.eio.connect.assert_awaited_once_with( - 'url', - headers='headers', - transports='transports', - engineio_path='path', + "url", + headers="headers", + transports="transports", + engineio_path="path", ) async def test_connect_error(self): c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock( - side_effect=engineio_exceptions.ConnectionError('foo') + side_effect=engineio_exceptions.ConnectionError("foo") ) - c.on('foo', mock.MagicMock(), namespace='/foo') - c.on('bar', mock.MagicMock(), namespace='/') + c.on("foo", mock.MagicMock(), namespace="/foo") + c.on("bar", mock.MagicMock(), namespace="/") with pytest.raises(exceptions.ConnectionError): await c.connect( - 'url', - headers='headers', - transports='transports', - socketio_path='path', + "url", + headers="headers", + transports="transports", + socketio_path="path", wait=False, ) @@ -153,12 +152,12 @@ class TestAsyncClient: c = async_client.AsyncClient() c.eio.connect = mock.AsyncMock() await c.connect( - 'url', + "url", wait=False, ) with pytest.raises(exceptions.ConnectionError): await c.connect( - 'url', + "url", wait=False, ) @@ -168,12 +167,12 @@ class TestAsyncClient: c._connect_event = mock.MagicMock() async def mock_connect(): - c.namespaces = {'/': '123'} + c.namespaces = {"/": "123"} return True c._connect_event.wait = mock_connect await c.connect( - 'url', + "url", wait=True, wait_timeout=0.01, ) @@ -186,22 +185,22 @@ class TestAsyncClient: async def mock_connect(): if c.namespaces == {}: - c.namespaces = {'/bar': '123'} + c.namespaces = {"/bar": "123"} return True - elif c.namespaces == {'/bar': '123'}: - c.namespaces = {'/bar': '123', '/foo': '456'} + if c.namespaces == {"/bar": "123"}: + c.namespaces = {"/bar": "123", "/foo": "456"} return True return False c._connect_event.wait = mock_connect await c.connect( - 'url', - namespaces=['/foo', '/bar'], + "url", + namespaces=["/foo", "/bar"], wait=True, wait_timeout=0.01, ) assert c.connected is True - assert c.namespaces == {'/bar': '123', '/foo': '456'} + assert c.namespaces == {"/bar": "123", "/foo": "456"} async def test_connect_timeout(self): c = async_client.AsyncClient() @@ -209,7 +208,7 @@ class TestAsyncClient: c.disconnect = mock.AsyncMock() with pytest.raises(exceptions.ConnectionError): await c.connect( - 'url', + "url", wait=True, wait_timeout=0.01, ) @@ -228,7 +227,7 @@ class TestAsyncClient: c = async_client.AsyncClient() c.eio.wait = mock.AsyncMock() c.sleep = mock.AsyncMock() - states = ['disconnected'] + states = ["disconnected"] async def fake_wait(): c.eio.state = states.pop(0) @@ -242,7 +241,7 @@ class TestAsyncClient: c = async_client.AsyncClient() c.eio.wait = mock.AsyncMock() c.sleep = mock.AsyncMock() - states = ['connected', 'disconnected'] + states = ["connected", "disconnected"] async def fake_wait(): c.eio.state = states.pop(0) @@ -255,190 +254,185 @@ class TestAsyncClient: async def test_emit_no_arguments(self): c = async_client.AsyncClient() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.AsyncMock() - await c.emit('foo') + await c.emit("foo") expected_packet = packet.Packet( - packet.EVENT, namespace='/', data=['foo'], id=None) + packet.EVENT, namespace="/", data=["foo"], id=None + ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_emit_one_argument(self): c = async_client.AsyncClient() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.AsyncMock() - await c.emit('foo', 'bar') + await c.emit("foo", "bar") expected_packet = packet.Packet( packet.EVENT, - namespace='/', - data=['foo', 'bar'], + namespace="/", + data=["foo", "bar"], id=None, ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_emit_one_argument_list(self): c = async_client.AsyncClient() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.AsyncMock() - await c.emit('foo', ['bar', 'baz']) + await c.emit("foo", ["bar", "baz"]) expected_packet = packet.Packet( packet.EVENT, - namespace='/', - data=['foo', ['bar', 'baz']], + namespace="/", + data=["foo", ["bar", "baz"]], id=None, ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_emit_two_arguments(self): c = async_client.AsyncClient() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.AsyncMock() - await c.emit('foo', ('bar', 'baz')) + await c.emit("foo", ("bar", "baz")) expected_packet = packet.Packet( packet.EVENT, - namespace='/', - data=['foo', 'bar', 'baz'], + namespace="/", + data=["foo", "bar", "baz"], id=None, ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_emit_namespace(self): c = async_client.AsyncClient() - c.namespaces = {'/foo': '1'} + c.namespaces = {"/foo": "1"} c._send_packet = mock.AsyncMock() - await c.emit('foo', namespace='/foo') + await c.emit("foo", namespace="/foo") expected_packet = packet.Packet( - packet.EVENT, namespace='/foo', data=['foo'], id=None) + packet.EVENT, namespace="/foo", data=["foo"], id=None + ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_emit_unknown_namespace(self): c = async_client.AsyncClient() - c.namespaces = {'/foo': '1'} + c.namespaces = {"/foo": "1"} with pytest.raises(exceptions.BadNamespaceError): - await c.emit('foo', namespace='/bar') + await c.emit("foo", namespace="/bar") 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'} - await c.emit('foo', callback='cb') + c.namespaces = {"/": "1"} + await c.emit("foo", callback="cb") expected_packet = packet.Packet( - packet.EVENT, namespace='/', data=['foo'], id=123) + packet.EVENT, namespace="/", data=["foo"], id=123 + ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) - c._generate_ack_id.assert_called_once_with('/', 'cb') + c._generate_ack_id.assert_called_once_with("/", "cb") async def test_emit_namespace_with_callback(self): c = async_client.AsyncClient() - c.namespaces = {'/foo': '1'} + c.namespaces = {"/foo": "1"} c._send_packet = mock.AsyncMock() c._generate_ack_id = mock.MagicMock(return_value=123) - await 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) + packet.EVENT, namespace="/foo", data=["foo"], id=123 + ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) - c._generate_ack_id.assert_called_once_with('/foo', 'cb') + c._generate_ack_id.assert_called_once_with("/foo", "cb") async def test_emit_binary(self): c = async_client.AsyncClient() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.AsyncMock() - await c.emit('foo', b'bar') + await c.emit("foo", b"bar") expected_packet = packet.Packet( packet.EVENT, - namespace='/', - data=['foo', b'bar'], + namespace="/", + data=["foo", b"bar"], id=None, ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_emit_not_binary(self): c = async_client.AsyncClient() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.AsyncMock() - await c.emit('foo', 'bar') + await c.emit("foo", "bar") expected_packet = packet.Packet( packet.EVENT, - namespace='/', - data=['foo', 'bar'], + namespace="/", + data=["foo", "bar"], id=None, ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_send(self): c = async_client.AsyncClient() c.emit = mock.AsyncMock() - await c.send('data', 'namespace', 'callback') + await c.send("data", "namespace", "callback") c.emit.assert_awaited_once_with( - 'message', data='data', namespace='namespace', callback='callback' + "message", data="data", namespace="namespace", callback="callback" ) async def test_send_with_defaults(self): c = async_client.AsyncClient() c.emit = mock.AsyncMock() - await c.send('data') + await c.send("data") c.emit.assert_awaited_once_with( - 'message', data='data', namespace=None, callback=None + "message", data="data", namespace=None, callback=None ) async def test_call(self): c = async_client.AsyncClient() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} async def fake_event_wait(): - c._generate_ack_id.call_args_list[0][0][1]('foo', 321) + c._generate_ack_id.call_args_list[0][0][1]("foo", 321) 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 await c.call('foo') == ('foo', 321) + assert await c.call("foo") == ("foo", 321) expected_packet = packet.Packet( - packet.EVENT, namespace='/', data=['foo'], id=123) + packet.EVENT, namespace="/", data=["foo"], id=123 + ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_call_with_timeout(self): c = async_client.AsyncClient() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} async def fake_event_wait(): await asyncio.sleep(1) @@ -448,64 +442,59 @@ class TestAsyncClient: c.eio = mock.MagicMock() c.eio.create_event.return_value.wait = fake_event_wait with pytest.raises(exceptions.TimeoutError): - await c.call('foo', timeout=0.01) + await c.call("foo", timeout=0.01) expected_packet = packet.Packet( - packet.EVENT, namespace='/', data=['foo'], id=123) + packet.EVENT, namespace="/", data=["foo"], id=123 + ) assert c._send_packet.await_count == 1 assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_disconnect(self): c = async_client.AsyncClient() c.connected = True - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._trigger_event = mock.AsyncMock() c._send_packet = mock.AsyncMock() c.eio = mock.MagicMock() c.eio.disconnect = mock.AsyncMock() - c.eio.state = 'connected' + c.eio.state = "connected" await c.disconnect() assert c.connected assert c._trigger_event.await_count == 0 assert c._send_packet.await_count == 1 - expected_packet = packet.Packet(packet.DISCONNECT, namespace='/') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/") assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) c.eio.disconnect.assert_awaited_once_with() async def test_disconnect_namespaces(self): c = async_client.AsyncClient() - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._trigger_event = mock.AsyncMock() c._send_packet = mock.AsyncMock() c.eio = mock.MagicMock() c.eio.disconnect = mock.AsyncMock() - c.eio.state = 'connected' + c.eio.state = "connected" 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') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/foo") assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) - expected_packet = packet.Packet(packet.DISCONNECT, namespace='/bar') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/bar") assert ( - c._send_packet.await_args_list[1][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[1][0][0].encode() == expected_packet.encode() ) 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' - c.eio.start_background_task.assert_called_once_with( - 'foo', 'bar', baz='baz' - ) + c.eio.start_background_task = mock.MagicMock(return_value="foo") + assert c.start_background_task("foo", "bar", baz="baz") == "foo" + c.eio.start_background_task.assert_called_once_with("foo", "bar", baz="baz") async def test_sleep(self): c = async_client.AsyncClient() @@ -516,25 +505,25 @@ class TestAsyncClient: async def test_send_packet(self): c = async_client.AsyncClient() c.eio.send = mock.AsyncMock() - await 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"') async def test_send_packet_binary(self): c = async_client.AsyncClient() c.eio.send = mock.AsyncMock() - await 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'), + mock.call(b"foo"), ] or c.eio.send.await_args_list == [ mock.call('51-{"num":0,"_placeholder":true}'), - mock.call(b'foo'), + mock.call(b"foo"), ] async def test_send_packet_default_binary(self): c = async_client.AsyncClient() c.eio.send = mock.AsyncMock() - await 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"') async def test_handle_connect(self): @@ -542,246 +531,229 @@ class TestAsyncClient: c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() c._send_packet = mock.AsyncMock() - await 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._trigger_event.assert_awaited_once_with("connect", namespace="/") c._send_packet.assert_not_awaited() async def test_handle_connect_with_namespaces(self): c = async_client.AsyncClient() - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() c._send_packet = mock.AsyncMock() - await 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'} + c._trigger_event.assert_awaited_once_with("connect", namespace="/") + assert c.namespaces == {"/": "3", "/foo": "1", "/bar": "2"} async def test_handle_connect_namespace(self): c = async_client.AsyncClient() - c.namespaces = {'/foo': '1'} + c.namespaces = {"/foo": "1"} c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() c._send_packet = mock.AsyncMock() - await c._handle_connect('/foo', {'sid': '123'}) - await 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'} + c._trigger_event.assert_awaited_once_with("connect", namespace="/bar") + assert c.namespaces == {"/foo": "1", "/bar": "2"} async def test_handle_disconnect(self): c = async_client.AsyncClient() c.connected = True c._trigger_event = mock.AsyncMock() - await c._handle_disconnect('/') - c._trigger_event.assert_any_await( - 'disconnect', '/', c.reason.SERVER_DISCONNECT - ) - c._trigger_event.assert_any_await('__disconnect_final', '/') + await c._handle_disconnect("/") + c._trigger_event.assert_any_await("disconnect", "/", c.reason.SERVER_DISCONNECT) + c._trigger_event.assert_any_await("__disconnect_final", "/") assert not c.connected - await c._handle_disconnect('/') + await c._handle_disconnect("/") assert c._trigger_event.await_count == 2 async def test_handle_disconnect_namespace(self): c = async_client.AsyncClient() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._trigger_event = mock.AsyncMock() - await c._handle_disconnect('/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'} + await c._handle_disconnect("/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', '/bar', - c.reason.SERVER_DISCONNECT) - c._trigger_event.assert_any_await('__disconnect_final', '/bar') + await c._handle_disconnect("/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 async def test_handle_disconnect_unknown_namespace(self): c = async_client.AsyncClient() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._trigger_event = mock.AsyncMock() - await c._handle_disconnect('/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'} + await c._handle_disconnect("/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 async def test_handle_disconnect_default_namespaces(self): c = async_client.AsyncClient() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._trigger_event = mock.AsyncMock() - await c._handle_disconnect('/') - 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'} + await c._handle_disconnect("/") + 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 async def test_handle_event(self): c = async_client.AsyncClient() c._trigger_event = mock.AsyncMock() - await c._handle_event('/', None, ['foo', ('bar', 'baz')]) - c._trigger_event.assert_awaited_once_with( - 'foo', '/', ('bar', 'baz') - ) + await c._handle_event("/", None, ["foo", ("bar", "baz")]) + c._trigger_event.assert_awaited_once_with("foo", "/", ("bar", "baz")) 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() - await c._handle_event('/', 123, ['foo', ('bar', 'baz')]) - c._trigger_event.assert_awaited_once_with( - 'foo', '/', ('bar', 'baz') - ) + await c._handle_event("/", 123, ["foo", ("bar", "baz")]) + c._trigger_event.assert_awaited_once_with("foo", "/", ("bar", "baz")) assert c._send_packet.await_count == 1 - expected_packet = packet.Packet( - packet.ACK, namespace='/', id=123, data=[]) + expected_packet = packet.Packet(packet.ACK, namespace="/", id=123, data=[]) assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_handle_event_with_id_one_argument(self): c = async_client.AsyncClient() - c._trigger_event = mock.AsyncMock(return_value='ret') + c._trigger_event = mock.AsyncMock(return_value="ret") c._send_packet = mock.AsyncMock() - await c._handle_event('/', 123, ['foo', ('bar', 'baz')]) - c._trigger_event.assert_awaited_once_with( - 'foo', '/', ('bar', 'baz') - ) + await c._handle_event("/", 123, ["foo", ("bar", "baz")]) + c._trigger_event.assert_awaited_once_with("foo", "/", ("bar", "baz")) assert c._send_packet.await_count == 1 - expected_packet = packet.Packet( - packet.ACK, namespace='/', id=123, data=['ret']) + expected_packet = packet.Packet(packet.ACK, namespace="/", id=123, data=["ret"]) assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) 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._trigger_event = mock.AsyncMock(return_value=["a", "b"]) c._send_packet = mock.AsyncMock() - await c._handle_event('/', 123, ['foo', ('bar', 'baz')]) - c._trigger_event.assert_awaited_once_with( - 'foo', '/', ('bar', 'baz') - ) + await c._handle_event("/", 123, ["foo", ("bar", "baz")]) + c._trigger_event.assert_awaited_once_with("foo", "/", ("bar", "baz")) assert c._send_packet.await_count == 1 expected_packet = packet.Packet( - packet.ACK, namespace='/', id=123, data=[['a', 'b']]) + packet.ACK, namespace="/", id=123, data=[["a", "b"]] + ) assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_handle_event_with_id_two_arguments(self): c = async_client.AsyncClient() - c._trigger_event = mock.AsyncMock(return_value=('a', 'b')) + c._trigger_event = mock.AsyncMock(return_value=("a", "b")) c._send_packet = mock.AsyncMock() - await c._handle_event('/', 123, ['foo', ('bar', 'baz')]) - c._trigger_event.assert_awaited_once_with( - 'foo', '/', ('bar', 'baz') - ) + await c._handle_event("/", 123, ["foo", ("bar", "baz")]) + c._trigger_event.assert_awaited_once_with("foo", "/", ("bar", "baz")) assert c._send_packet.await_count == 1 expected_packet = packet.Packet( - packet.ACK, namespace='/', id=123, data=['a', 'b']) + packet.ACK, namespace="/", id=123, data=["a", "b"] + ) assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) async def test_handle_ack(self): c = async_client.AsyncClient() mock_cb = mock.MagicMock() - c.callbacks['/foo'] = {123: mock_cb} - await c._handle_ack('/foo', 123, ['bar', 'baz']) - mock_cb.assert_called_once_with('bar', 'baz') - assert 123 not in c.callbacks['/foo'] + c.callbacks["/foo"] = {123: mock_cb} + await c._handle_ack("/foo", 123, ["bar", "baz"]) + mock_cb.assert_called_once_with("bar", "baz") + assert 123 not in c.callbacks["/foo"] async def test_handle_ack_async(self): c = async_client.AsyncClient() mock_cb = mock.AsyncMock() - c.callbacks['/foo'] = {123: mock_cb} - await c._handle_ack('/foo', 123, ['bar', 'baz']) - mock_cb.assert_awaited_once_with('bar', 'baz') - assert 123 not in c.callbacks['/foo'] + c.callbacks["/foo"] = {123: mock_cb} + await c._handle_ack("/foo", 123, ["bar", "baz"]) + mock_cb.assert_awaited_once_with("bar", "baz") + assert 123 not in c.callbacks["/foo"] async def test_handle_ack_not_found(self): c = async_client.AsyncClient() mock_cb = mock.MagicMock() - c.callbacks['/foo'] = {123: mock_cb} - await c._handle_ack('/foo', 124, ['bar', 'baz']) + c.callbacks["/foo"] = {123: mock_cb} + await c._handle_ack("/foo", 124, ["bar", "baz"]) mock_cb.assert_not_called() - assert 123 in c.callbacks['/foo'] + assert 123 in c.callbacks["/foo"] 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'} - await c._handle_error('/', 'error') + c.namespaces = {"/foo": "1", "/bar": "2"} + await c._handle_error("/", "error") assert c.namespaces == {} assert not c.connected c._connect_event.set.assert_called_once_with() - c._trigger_event.assert_awaited_once_with( - 'connect_error', '/', 'error' - ) + c._trigger_event.assert_awaited_once_with("connect_error", "/", "error") 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'} - await c._handle_error('/', None) + c.namespaces = {"/foo": "1", "/bar": "2"} + 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', '/') + c._trigger_event.assert_awaited_once_with("connect_error", "/") async def test_handle_error_namespace(self): c = async_client.AsyncClient() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() - await c._handle_error('/bar', ['error', 'message']) - assert c.namespaces == {'/foo': '1'} + await 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.assert_awaited_once_with( - 'connect_error', '/bar', 'error', 'message' + "connect_error", "/bar", "error", "message" ) async def test_handle_error_namespace_with_no_arguments(self): c = async_client.AsyncClient() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._connect_event = mock.MagicMock() c._trigger_event = mock.AsyncMock() - await c._handle_error('/bar', None) - assert c.namespaces == {'/foo': '1'} + 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') + c._trigger_event.assert_awaited_once_with("connect_error", "/bar") async def test_handle_error_unknown_namespace(self): c = async_client.AsyncClient() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._connect_event = mock.MagicMock() - await c._handle_error('/baz', 'error') - assert c.namespaces == {'/foo': '1', '/bar': '2'} + await c._handle_error("/baz", "error") + assert c.namespaces == {"/foo": "1", "/bar": "2"} assert c.connected c._connect_event.set.assert_called_once_with() @@ -789,44 +761,44 @@ class TestAsyncClient: c = async_client.AsyncClient() handler = mock.MagicMock() catchall_handler = mock.MagicMock() - c.on('foo', handler) - c.on('*', catchall_handler) - 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) + c.on("foo", handler) + c.on("*", catchall_handler) + 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) 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') - 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) + c.on("foo", handler, namespace="/bar") + c.on("*", catchall_handler, namespace="/bar") + 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) async def test_trigger_legacy_disconnect_event(self): c = async_client.AsyncClient() - @c.on('disconnect') + @c.on("disconnect") def baz(): - return 'baz' + return "baz" - r = await c._trigger_event('disconnect', '/', 'foo') - assert r == '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') + @c.on("disconnect") async def baz(): - return 'baz' + return "baz" - r = await c._trigger_event('disconnect', '/', 'foo') - assert r == 'baz' + r = await c._trigger_event("disconnect", "/", "foo") + assert r == "baz" async def test_trigger_event_class_namespace(self): c = async_client.AsyncClient() @@ -837,41 +809,41 @@ class TestAsyncClient: result.append(a) result.append(b) - c.register_namespace(MyNamespace('/')) - await c._trigger_event('foo', '/', 1, '2') - assert result == [1, '2'] + c.register_namespace(MyNamespace("/")) + await c._trigger_event("foo", "/", 1, "2") + assert result == [1, "2"] async def test_trigger_event_with_catchall_class_namespace(self): result = {} class MyNamespace(async_namespace.AsyncClientNamespace): def on_connect(self, ns): - result['result'] = (ns,) + result["result"] = (ns,) def on_disconnect(self, ns): - result['result'] = ('disconnect', ns) + result["result"] = ("disconnect", ns) def on_foo(self, ns, data): - result['result'] = (ns, data) + result["result"] = (ns, data) def on_bar(self, ns): - result['result'] = 'bar' + ns + result["result"] = "bar" + ns def on_baz(self, ns, data1, data2): - result['result'] = (ns, data1, data2) + result["result"] = (ns, data1, data2) c = async_client.AsyncClient() - c.register_namespace(MyNamespace('*')) - await c._trigger_event('connect', '/foo') - assert result['result'] == ('/foo',) - await c._trigger_event('foo', '/foo', 'a') - assert result['result'] == ('/foo', 'a') - await c._trigger_event('bar', '/foo') - assert result['result'] == 'bar/foo' - await c._trigger_event('baz', '/foo', 'a', 'b') - assert result['result'] == ('/foo', 'a', 'b') - await c._trigger_event('disconnect', '/foo') - assert result['result'] == ('disconnect', '/foo') + c.register_namespace(MyNamespace("*")) + await c._trigger_event("connect", "/foo") + assert result["result"] == ("/foo",) + await c._trigger_event("foo", "/foo", "a") + assert result["result"] == ("/foo", "a") + await c._trigger_event("bar", "/foo") + assert result["result"] == "bar/foo" + await c._trigger_event("baz", "/foo", "a", "b") + assert result["result"] == ("/foo", "a", "b") + await c._trigger_event("disconnect", "/foo") + assert result["result"] == ("disconnect", "/foo") async def test_trigger_event_unknown_namespace(self): c = async_client.AsyncClient() @@ -882,19 +854,19 @@ class TestAsyncClient: result.append(a) result.append(b) - c.register_namespace(MyNamespace('/')) - await c._trigger_event('foo', '/bar', 1, '2') + c.register_namespace(MyNamespace("/")) + await c._trigger_event("foo", "/bar", 1, "2") assert result == [] @mock.patch( - 'asyncio.wait_for', + "asyncio.wait_for", new_callable=mock.AsyncMock, side_effect=asyncio.TimeoutError, ) - @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) + @mock.patch("socketio.client.random.random", side_effect=[1, 0, 0.5]) async def test_handle_reconnect(self, random, wait_for): c = async_client.AsyncClient() - c._reconnect_task = 'foo' + c._reconnect_task = "foo" c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] ) @@ -908,14 +880,14 @@ class TestAsyncClient: assert c._reconnect_task is None @mock.patch( - 'asyncio.wait_for', + "asyncio.wait_for", new_callable=mock.AsyncMock, side_effect=asyncio.TimeoutError, ) - @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) + @mock.patch("socketio.client.random.random", side_effect=[1, 0, 0.5]) async def test_handle_reconnect_max_delay(self, random, wait_for): c = async_client.AsyncClient(reconnection_delay_max=3) - c._reconnect_task = 'foo' + c._reconnect_task = "foo" c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] ) @@ -929,15 +901,15 @@ class TestAsyncClient: assert c._reconnect_task is None @mock.patch( - 'asyncio.wait_for', + "asyncio.wait_for", new_callable=mock.AsyncMock, side_effect=asyncio.TimeoutError, ) - @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) + @mock.patch("socketio.client.random.random", side_effect=[1, 0, 0.5]) 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' + c.connection_namespaces = ["/"] + c._reconnect_task = "foo" c._trigger_event = mock.AsyncMock() c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] @@ -948,20 +920,19 @@ class TestAsyncClient: 1.5, 1.5, ] - assert c._reconnect_task == 'foo' - c._trigger_event.assert_awaited_once_with('__disconnect_final', - namespace='/') + assert c._reconnect_task == "foo" + c._trigger_event.assert_awaited_once_with("__disconnect_final", namespace="/") @mock.patch( - 'asyncio.wait_for', + "asyncio.wait_for", new_callable=mock.AsyncMock, side_effect=[asyncio.TimeoutError, None], ) - @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) + @mock.patch("socketio.client.random.random", side_effect=[1, 0, 0.5]) async def test_handle_reconnect_aborted(self, random, wait_for): c = async_client.AsyncClient(logger=True) - c.connection_namespaces = ['/'] - c._reconnect_task = 'foo' + c.connection_namespaces = ["/"] + c._reconnect_task = "foo" c._trigger_event = mock.AsyncMock() c.connect = mock.AsyncMock( side_effect=[ValueError, exceptions.ConnectionError, None] @@ -972,56 +943,52 @@ class TestAsyncClient: 1.5, 1.5, ] - assert c._reconnect_task == 'foo' - c._trigger_event.assert_awaited_once_with('__disconnect_final', - namespace='/') + assert c._reconnect_task == "foo" + c._trigger_event.assert_awaited_once_with("__disconnect_final", namespace="/") async def test_shutdown_disconnect(self): c = async_client.AsyncClient() c.connected = True - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._trigger_event = mock.AsyncMock() c._send_packet = mock.AsyncMock() c.eio = mock.MagicMock() c.eio.disconnect = mock.AsyncMock() - c.eio.state = 'connected' + c.eio.state = "connected" await c.shutdown() assert c._trigger_event.await_count == 0 assert c._send_packet.await_count == 1 - expected_packet = packet.Packet(packet.DISCONNECT, namespace='/') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/") assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) c.eio.disconnect.assert_awaited_once_with() async def test_shutdown_disconnect_namespaces(self): c = async_client.AsyncClient() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._trigger_event = mock.AsyncMock() c._send_packet = mock.AsyncMock() c.eio = mock.MagicMock() c.eio.disconnect = mock.AsyncMock() - c.eio.state = 'connected' + c.eio.state = "connected" 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') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/foo") assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) - expected_packet = packet.Packet(packet.DISCONNECT, namespace='/bar') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/bar") assert ( - c._send_packet.await_args_list[1][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[1][0][0].encode() == expected_packet.encode() ) - @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) + @mock.patch("socketio.client.random.random", side_effect=[1, 0, 0.5]) async def test_shutdown_reconnect(self, random): c = async_client.AsyncClient() - c.connection_namespaces = ['/'] + c.connection_namespaces = ["/"] c._reconnect_task = mock.AsyncMock()() c._trigger_event = mock.AsyncMock() c.connect = mock.AsyncMock(side_effect=exceptions.ConnectionError) @@ -1033,53 +1000,44 @@ class TestAsyncClient: await task await r() - c._trigger_event.assert_awaited_once_with('__disconnect_final', - namespace='/') + c._trigger_event.assert_awaited_once_with("__disconnect_final", namespace="/") async def test_handle_eio_connect(self): c = async_client.AsyncClient() - c.connection_namespaces = ['/', '/foo'] - c.connection_auth = 'auth' + c.connection_namespaces = ["/", "/foo"] + c.connection_auth = "auth" c._send_packet = mock.AsyncMock() - c.eio.sid = 'foo' + c.eio.sid = "foo" assert c.sid is None await c._handle_eio_connect() - assert c.sid == 'foo' + assert c.sid == "foo" assert c._send_packet.await_count == 2 - expected_packet = packet.Packet( - packet.CONNECT, data='auth', namespace='/') + expected_packet = packet.Packet(packet.CONNECT, data="auth", namespace="/") assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) - expected_packet = packet.Packet( - packet.CONNECT, data='auth', namespace='/foo') + expected_packet = packet.Packet(packet.CONNECT, data="auth", namespace="/foo") assert ( - c._send_packet.await_args_list[1][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[1][0][0].encode() == expected_packet.encode() ) async def test_handle_eio_connect_function(self): c = async_client.AsyncClient() - c.connection_namespaces = ['/', '/foo'] - c.connection_auth = lambda: 'auth' + c.connection_namespaces = ["/", "/foo"] + c.connection_auth = lambda: "auth" c._send_packet = mock.AsyncMock() - c.eio.sid = 'foo' + c.eio.sid = "foo" assert c.sid is None await c._handle_eio_connect() - assert c.sid == 'foo' + assert c.sid == "foo" assert c._send_packet.await_count == 2 - expected_packet = packet.Packet( - packet.CONNECT, data='auth', namespace='/') + expected_packet = packet.Packet(packet.CONNECT, data="auth", namespace="/") assert ( - c._send_packet.await_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[0][0][0].encode() == expected_packet.encode() ) - expected_packet = packet.Packet( - packet.CONNECT, data='auth', namespace='/foo') + expected_packet = packet.Packet(packet.CONNECT, data="auth", namespace="/foo") assert ( - c._send_packet.await_args_list[1][0][0].encode() - == expected_packet.encode() + c._send_packet.await_args_list[1][0][0].encode() == expected_packet.encode() ) async def test_handle_eio_message(self): @@ -1091,100 +1049,98 @@ class TestAsyncClient: c._handle_error = mock.AsyncMock() await c._handle_eio_message('0{"sid":"123"}') - c._handle_connect.assert_awaited_with(None, {'sid': '123'}) + c._handle_connect.assert_awaited_with(None, {"sid": "123"}) await c._handle_eio_message('0/foo,{"sid":"123"}') - c._handle_connect.assert_awaited_with('/foo', {'sid': '123'}) - await c._handle_eio_message('1') + c._handle_connect.assert_awaited_with("/foo", {"sid": "123"}) + await c._handle_eio_message("1") c._handle_disconnect.assert_awaited_with(None) - await c._handle_eio_message('1/foo') - c._handle_disconnect.assert_awaited_with('/foo') + await c._handle_eio_message("1/foo") + c._handle_disconnect.assert_awaited_with("/foo") await c._handle_eio_message('2["foo"]') - c._handle_event.assert_awaited_with(None, None, ['foo']) + c._handle_event.assert_awaited_with(None, None, ["foo"]) await c._handle_eio_message('3/foo,["bar"]') - c._handle_ack.assert_awaited_with('/foo', None, ['bar']) - await c._handle_eio_message('4') + c._handle_ack.assert_awaited_with("/foo", None, ["bar"]) + await c._handle_eio_message("4") c._handle_error.assert_awaited_with(None, None) await c._handle_eio_message('4"foo"') - c._handle_error.assert_awaited_with(None, 'foo') + c._handle_error.assert_awaited_with(None, "foo") await c._handle_eio_message('4["foo"]') - c._handle_error.assert_awaited_with(None, ['foo']) - await c._handle_eio_message('4/foo') - c._handle_error.assert_awaited_with('/foo', None) + c._handle_error.assert_awaited_with(None, ["foo"]) + await c._handle_eio_message("4/foo") + c._handle_error.assert_awaited_with("/foo", None) await c._handle_eio_message('4/foo,["foo","bar"]') - c._handle_error.assert_awaited_with('/foo', ['foo', 'bar']) + c._handle_error.assert_awaited_with("/foo", ["foo", "bar"]) await c._handle_eio_message('51-{"_placeholder":true,"num":0}') assert c._binary_packet.packet_type == packet.BINARY_EVENT - await c._handle_eio_message(b'foo') - c._handle_event.assert_awaited_with(None, None, b'foo') + await c._handle_eio_message(b"foo") + c._handle_event.assert_awaited_with(None, None, b"foo") 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 - 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'} - ) + 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): - await c._handle_eio_message('9') + await c._handle_eio_message("9") async def test_eio_disconnect(self): c = async_client.AsyncClient() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c.connected = True c._trigger_event = mock.AsyncMock() c.start_background_task = mock.MagicMock() - c.sid = 'foo' - c.eio.state = 'connected' - await c._handle_eio_disconnect('foo') - c._trigger_event.assert_awaited_once_with('disconnect', '/', 'foo') + c.sid = "foo" + c.eio.state = "connected" + await c._handle_eio_disconnect("foo") + c._trigger_event.assert_awaited_once_with("disconnect", "/", "foo") assert c.sid is None assert not c.connected async def test_eio_disconnect_namespaces(self): c = async_client.AsyncClient(reconnection=False) - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c.connected = True c._trigger_event = mock.AsyncMock() - c.sid = 'foo' - c.eio.state = 'connected' + c.sid = "foo" + c.eio.state = "connected" 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) + 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 async def test_eio_disconnect_reconnect(self): c = async_client.AsyncClient(reconnection=True) c.start_background_task = mock.MagicMock() - c.eio.state = 'connected' + c.eio.state = "connected" 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' + c.eio.state = "disconnected" await c._handle_eio_disconnect(c.reason.CLIENT_DISCONNECT) c.start_background_task.assert_not_called() async def test_eio_disconnect_no_reconnect(self): c = async_client.AsyncClient(reconnection=False) - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c.connected = True c._trigger_event = mock.AsyncMock() c.start_background_task = mock.MagicMock() - c.sid = 'foo' - c.eio.state = 'connected' + c.sid = "foo" + c.eio.state = "connected" 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', '/') + 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 aa89064..64a25b5 100644 --- a/tests/async/test_manager.py +++ b/tests/async/test_manager.py @@ -1,7 +1,6 @@ from unittest import mock -from socketio import async_manager -from socketio import packet +from socketio import async_manager, packet class TestAsyncManager: @@ -23,360 +22,320 @@ class TestAsyncManager: self.bm.initialize() 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] - assert sid in self.bm.rooms['/foo'][sid] - assert dict(self.bm.rooms['/foo'][None]) == {sid: '123'} - assert dict(self.bm.rooms['/foo'][sid]) == {sid: '123'} - assert self.bm.sid_from_eio_sid('123', '/foo') == sid + 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] + assert sid in self.bm.rooms["/foo"][sid] + assert dict(self.bm.rooms["/foo"][None]) == {sid: "123"} + assert dict(self.bm.rooms["/foo"][sid]) == {sid: "123"} + assert self.bm.sid_from_eio_sid("123", "/foo") == sid 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]} - assert not self.bm.is_connected(sid1, '/foo') - assert self.bm.pre_disconnect(sid2, '/foo') == '456' - assert self.bm.pending_disconnect == {'/foo': [sid1, sid2]} - assert not self.bm.is_connected(sid2, '/foo') - await self.bm.disconnect(sid1, '/foo') - assert self.bm.pending_disconnect == {'/foo': [sid2]} - await self.bm.disconnect(sid2, '/foo') + 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]} + assert not self.bm.is_connected(sid1, "/foo") + assert self.bm.pre_disconnect(sid2, "/foo") == "456" + assert self.bm.pending_disconnect == {"/foo": [sid1, sid2]} + assert not self.bm.is_connected(sid2, "/foo") + await self.bm.disconnect(sid1, "/foo") + assert self.bm.pending_disconnect == {"/foo": [sid2]} + await self.bm.disconnect(sid2, "/foo") assert self.bm.pending_disconnect == {} 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'} + 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"} 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') - await self.bm.disconnect(sid1, '/') - assert not self.bm.is_connected(sid1, '/') - assert self.bm.is_connected(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'} + 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") + await self.bm.disconnect(sid1, "/") + assert not self.bm.is_connected(sid1, "/") + assert self.bm.is_connected(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"} 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'} + 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"} 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') + 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 == {} 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') - await self.bm.disconnect(sid2, '/foo') + 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") + await self.bm.disconnect(sid2, "/foo") assert sid2 not in self.bm.callbacks - await self.bm.disconnect(sid1, '/') + await self.bm.disconnect(sid1, "/") assert sid1 not in self.bm.callbacks assert sid3 in self.bm.callbacks async def test_trigger_sync_callback(self): - sid1 = await self.bm.connect('123', '/') - sid2 = await self.bm.connect('123', '/foo') + 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) - await self.bm.trigger_callback(sid1, id1, ['foo']) - await 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') + cb.assert_any_call("foo") + cb.assert_any_call("bar", "baz") async def test_trigger_async_callback(self): - sid1 = await self.bm.connect('123', '/') - sid2 = await self.bm.connect('123', '/foo') + 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) - await self.bm.trigger_callback(sid1, id1, ['foo']) - await 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') + cb.assert_any_await("foo") + cb.assert_any_await("bar", "baz") async def test_invalid_callback(self): - sid = await self.bm.connect('123', '/') + sid = await self.bm.connect("123", "/") cb = mock.MagicMock() id = self.bm._generate_ack_id(sid, cb) # these should not raise an exception - await self.bm.trigger_callback('xxx', id, ['foo']) - await 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 async def test_get_namespaces(self): assert list(self.bm.get_namespaces()) == [] - await self.bm.connect('123', '/') - await 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 + assert "/" in namespaces + assert "/foo" in namespaces 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)) + 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 - assert (sid1, '123') in participants - assert (sid2, '456') in participants - assert (sid3, '789') not in participants + assert (sid1, "123") in participants + assert (sid2, "456") in participants + assert (sid3, "789") not in participants 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') + sid = await self.bm.connect("123", "/foo") + await self.bm.leave_room(sid, "/foo", "baz") + await self.bm.leave_room(sid, "/bar", "baz") async def test_no_room(self): - rooms = self.bm.get_rooms('123', '/foo') - assert [] == rooms + rooms = self.bm.get_rooms("123", "/foo") + assert rooms == [] 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') - assert 'bar' not in self.bm.rooms['/foo'] + 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") + assert "bar" not in self.bm.rooms["/foo"] async def test_close_invalid_room(self): - self.bm.close_room('bar', '/foo') + self.bm.close_room("bar", "/foo") 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') + 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 + assert "bar" in r 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 - ) + 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] \ - == '123' + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] == "123" pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"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' - ) + 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] \ - == '123' - assert self.bm.server._send_eio_packet.await_args_list[1][0][0] \ - == '456' + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] == "123" + assert self.bm.server._send_eio_packet.await_args_list[1][0][0] == "456" 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.await_args_list[1][0][1] == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' 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']) + 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' - assert self.bm.server._send_eio_packet.await_args_list[1][0][0] \ - == '456' - assert self.bm.server._send_eio_packet.await_args_list[2][0][0] \ - == '789' + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] == "123" + assert self.bm.server._send_eio_packet.await_args_list[1][0][0] == "456" + assert self.bm.server._send_eio_packet.await_args_list[2][0][0] == "789" 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.await_args_list[2][0][1] \ - == pkt + assert self.bm.server._send_eio_packet.await_args_list[1][0][1] == pkt + assert self.bm.server._send_eio_packet.await_args_list[2][0][1] == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' 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') + 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' - assert self.bm.server._send_eio_packet.await_args_list[1][0][0] \ - == '456' - assert self.bm.server._send_eio_packet.await_args_list[2][0][0] \ - == '789' + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] == "123" + assert self.bm.server._send_eio_packet.await_args_list[1][0][0] == "456" + assert self.bm.server._send_eio_packet.await_args_list[2][0][0] == "789" 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.await_args_list[2][0][1] \ - == pkt + assert self.bm.server._send_eio_packet.await_args_list[1][0][1] == pkt + assert self.bm.server._send_eio_packet.await_args_list[2][0][1] == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' 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 - ) + 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] \ - == '123' - assert self.bm.server._send_eio_packet.await_args_list[1][0][0] \ - == '789' + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] == "123" + assert self.bm.server._send_eio_packet.await_args_list[1][0][0] == "789" 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.await_args_list[1][0][1] == pkt assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' 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') + 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', + "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] \ - == '456' + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] == "456" pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' async def test_emit_with_callback(self): - sid = await self.bm.connect('123', '/foo') + sid = await self.bm.connect("123", "/foo") self.bm._generate_ack_id = mock.MagicMock() self.bm._generate_ack_id.return_value = 11 - await self.bm.emit( - 'my event', {'foo': 'bar'}, namespace='/foo', callback='cb' - ) - self.bm._generate_ack_id.assert_called_once_with(sid, '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 - assert self.bm.server._send_packet.await_args_list[0][0][0] \ - == '123' + assert self.bm.server._send_packet.await_args_list[0][0][0] == "123" pkt = self.bm.server._send_packet.await_args_list[0][0][1] assert pkt.encode() == '2/foo,11["my event",{"foo":"bar"}]' async def test_emit_to_invalid_room(self): - await self.bm.emit('my event', {'foo': 'bar'}, namespace='/', - room='123') + await self.bm.emit("my event", {"foo": "bar"}, namespace="/", room="123") async def test_emit_to_invalid_namespace(self): - await self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo') + await self.bm.emit("my event", {"foo": "bar"}, namespace="/foo") 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 - ) + 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] \ - == '123' + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] == "123" pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event","foo","bar"]' 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 - ) + 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] \ - == '123' + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] == "123" pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event",["foo","bar"]]' 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 - ) + 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] \ - == '123' + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] == "123" pkt = self.bm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event"]' 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 - ) + 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] \ - == '123' + assert self.bm.server._send_eio_packet.await_args_list[0][0][0] == "123" 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.await_args_list[1][0][0] \ - == '123' + assert self.bm.server._send_eio_packet.await_args_list[1][0][0] == "123" pkt = self.bm.server._send_eio_packet.await_args_list[1][0][1] - assert pkt.encode() == b'my binary data' + assert pkt.encode() == b"my binary data" diff --git a/tests/async/test_namespace.py b/tests/async/test_namespace.py index 526d676..b76e879 100644 --- a/tests/async/test_namespace.py +++ b/tests/async/test_namespace.py @@ -9,380 +9,366 @@ class TestAsyncNamespace: class MyNamespace(async_namespace.AsyncNamespace): async def on_connect(self, sid, environ): - result['result'] = (sid, environ) + result["result"] = (sid, environ) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - await ns.trigger_event('connect', 'sid', {'foo': 'bar'}) - assert result['result'] == ('sid', {'foo': 'bar'}) + await ns.trigger_event("connect", "sid", {"foo": "bar"}) + assert result["result"] == ("sid", {"foo": "bar"}) async def test_disconnect_event(self): result = {} class MyNamespace(async_namespace.AsyncNamespace): async def on_disconnect(self, sid, reason): - result['result'] = (sid, reason) + result["result"] = (sid, reason) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - await ns.trigger_event('disconnect', 'sid', 'foo') - assert result['result'] == ('sid', 'foo') + 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 + result["result"] = sid - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - await ns.trigger_event('disconnect', 'sid', 'foo') - assert result['result'] == 'sid' + 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 + result["result"] = sid - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - await ns.trigger_event('disconnect', 'sid', 'foo') - assert result['result'] == 'sid' + await ns.trigger_event("disconnect", "sid", "foo") + assert result["result"] == "sid" async def test_sync_event(self): result = {} class MyNamespace(async_namespace.AsyncNamespace): def on_custom_message(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - await ns.trigger_event('custom_message', 'sid', {'data': 'data'}) - assert result['result'] == ('sid', {'data': 'data'}) + await ns.trigger_event("custom_message", "sid", {"data": "data"}) + assert result["result"] == ("sid", {"data": "data"}) async def test_async_event(self): result = {} class MyNamespace(async_namespace.AsyncNamespace): async def on_custom_message(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - await ns.trigger_event('custom_message', 'sid', {'data': 'data'}) - assert result['result'] == ('sid', {'data': 'data'}) + await ns.trigger_event("custom_message", "sid", {"data": "data"}) + assert result["result"] == ("sid", {"data": "data"}) async def test_event_not_found(self): result = {} class MyNamespace(async_namespace.AsyncNamespace): async def on_custom_message(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - await ns.trigger_event('another_custom_message', 'sid', - {'data': 'data'}) + await ns.trigger_event("another_custom_message", "sid", {"data": "data"}) assert result == {} async def test_emit(self): - ns = async_namespace.AsyncNamespace('/foo') + ns = async_namespace.AsyncNamespace("/foo") mock_server = mock.MagicMock() mock_server.emit = mock.AsyncMock() ns._set_server(mock_server) - await 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', - data='data', - to='room', + "ev", + data="data", + to="room", room=None, - skip_sid='skip', - namespace='/foo', - callback='cb', + skip_sid="skip", + namespace="/foo", + callback="cb", ignore_queue=False, ) await ns.emit( - 'ev', - data='data', - room='room', - skip_sid='skip', - namespace='/bar', - callback='cb', + "ev", + data="data", + room="room", + skip_sid="skip", + namespace="/bar", + callback="cb", ignore_queue=True, ) ns.server.emit.assert_awaited_with( - 'ev', - data='data', + "ev", + data="data", to=None, - room='room', - skip_sid='skip', - namespace='/bar', - callback='cb', + room="room", + skip_sid="skip", + namespace="/bar", + callback="cb", ignore_queue=True, ) async def test_send(self): - ns = async_namespace.AsyncNamespace('/foo') + ns = async_namespace.AsyncNamespace("/foo") mock_server = mock.MagicMock() mock_server.send = mock.AsyncMock() ns._set_server(mock_server) - await 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', + "data", + to="room", room=None, - skip_sid='skip', - namespace='/foo', - callback='cb', + skip_sid="skip", + namespace="/foo", + callback="cb", ignore_queue=False, ) await ns.send( - data='data', - room='room', - skip_sid='skip', - namespace='/bar', - callback='cb', + data="data", + room="room", + skip_sid="skip", + namespace="/bar", + callback="cb", ignore_queue=True, ) ns.server.send.assert_awaited_with( - 'data', + "data", to=None, - room='room', - skip_sid='skip', - namespace='/bar', - callback='cb', + room="room", + skip_sid="skip", + namespace="/bar", + callback="cb", ignore_queue=True, ) async def test_call(self): - ns = async_namespace.AsyncNamespace('/foo') + ns = async_namespace.AsyncNamespace("/foo") mock_server = mock.MagicMock() mock_server.call = mock.AsyncMock() ns._set_server(mock_server) - await ns.call('ev', data='data', to='sid') + await ns.call("ev", data="data", to="sid") ns.server.call.assert_awaited_with( - 'ev', - data='data', - to='sid', + "ev", + data="data", + to="sid", sid=None, - namespace='/foo', + namespace="/foo", timeout=None, ignore_queue=False, ) - await 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', + "ev", + data="data", to=None, - sid='sid', - namespace='/bar', + sid="sid", + namespace="/bar", timeout=45, ignore_queue=True, ) async def test_enter_room(self): - ns = async_namespace.AsyncNamespace('/foo') + ns = async_namespace.AsyncNamespace("/foo") mock_server = mock.MagicMock() mock_server.enter_room = mock.AsyncMock() ns._set_server(mock_server) - await ns.enter_room('sid', 'room') - ns.server.enter_room.assert_awaited_with( - 'sid', 'room', namespace='/foo' - ) - await ns.enter_room('sid', 'room', namespace='/bar') - ns.server.enter_room.assert_awaited_with( - 'sid', 'room', namespace='/bar' - ) + await ns.enter_room("sid", "room") + ns.server.enter_room.assert_awaited_with("sid", "room", namespace="/foo") + await ns.enter_room("sid", "room", namespace="/bar") + ns.server.enter_room.assert_awaited_with("sid", "room", namespace="/bar") async def test_leave_room(self): - ns = async_namespace.AsyncNamespace('/foo') + ns = async_namespace.AsyncNamespace("/foo") mock_server = mock.MagicMock() mock_server.leave_room = mock.AsyncMock() ns._set_server(mock_server) - await ns.leave_room('sid', 'room') - ns.server.leave_room.assert_awaited_with( - 'sid', 'room', namespace='/foo' - ) - await ns.leave_room('sid', 'room', namespace='/bar') - ns.server.leave_room.assert_awaited_with( - 'sid', 'room', namespace='/bar' - ) + await ns.leave_room("sid", "room") + ns.server.leave_room.assert_awaited_with("sid", "room", namespace="/foo") + await ns.leave_room("sid", "room", namespace="/bar") + ns.server.leave_room.assert_awaited_with("sid", "room", namespace="/bar") async def test_close_room(self): - ns = async_namespace.AsyncNamespace('/foo') + ns = async_namespace.AsyncNamespace("/foo") mock_server = mock.MagicMock() mock_server.close_room = mock.AsyncMock() ns._set_server(mock_server) - await ns.close_room('room') - ns.server.close_room.assert_awaited_with('room', namespace='/foo') - await ns.close_room('room', namespace='/bar') - ns.server.close_room.assert_awaited_with('room', namespace='/bar') + await ns.close_room("room") + ns.server.close_room.assert_awaited_with("room", namespace="/foo") + await ns.close_room("room", namespace="/bar") + ns.server.close_room.assert_awaited_with("room", namespace="/bar") async def test_rooms(self): - ns = async_namespace.AsyncNamespace('/foo') + ns = async_namespace.AsyncNamespace("/foo") ns._set_server(mock.MagicMock()) - ns.rooms('sid') - ns.server.rooms.assert_called_with('sid', namespace='/foo') - ns.rooms('sid', namespace='/bar') - ns.server.rooms.assert_called_with('sid', namespace='/bar') + ns.rooms("sid") + ns.server.rooms.assert_called_with("sid", namespace="/foo") + ns.rooms("sid", namespace="/bar") + ns.server.rooms.assert_called_with("sid", namespace="/bar") async def test_session(self): - ns = async_namespace.AsyncNamespace('/foo') + 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) - await ns.get_session('sid') - ns.server.get_session.assert_awaited_with('sid', namespace='/foo') - await ns.get_session('sid', namespace='/bar') - ns.server.get_session.assert_awaited_with('sid', namespace='/bar') - await ns.save_session('sid', {'a': 'b'}) - ns.server.save_session.assert_awaited_with( - 'sid', {'a': 'b'}, namespace='/foo' - ) - await ns.save_session('sid', {'a': 'b'}, namespace='/bar') - ns.server.save_session.assert_awaited_with( - 'sid', {'a': 'b'}, namespace='/bar' - ) - ns.session('sid') - ns.server.session.assert_called_with('sid', namespace='/foo') - ns.session('sid', namespace='/bar') - ns.server.session.assert_called_with('sid', namespace='/bar') + await ns.get_session("sid") + ns.server.get_session.assert_awaited_with("sid", namespace="/foo") + await ns.get_session("sid", namespace="/bar") + ns.server.get_session.assert_awaited_with("sid", namespace="/bar") + await ns.save_session("sid", {"a": "b"}) + ns.server.save_session.assert_awaited_with("sid", {"a": "b"}, namespace="/foo") + await ns.save_session("sid", {"a": "b"}, namespace="/bar") + ns.server.save_session.assert_awaited_with("sid", {"a": "b"}, namespace="/bar") + ns.session("sid") + ns.server.session.assert_called_with("sid", namespace="/foo") + ns.session("sid", namespace="/bar") + ns.server.session.assert_called_with("sid", namespace="/bar") async def test_disconnect(self): - ns = async_namespace.AsyncNamespace('/foo') + ns = async_namespace.AsyncNamespace("/foo") mock_server = mock.MagicMock() mock_server.disconnect = mock.AsyncMock() ns._set_server(mock_server) - await ns.disconnect('sid') - ns.server.disconnect.assert_awaited_with('sid', namespace='/foo') - await ns.disconnect('sid', namespace='/bar') - ns.server.disconnect.assert_awaited_with('sid', namespace='/bar') + await ns.disconnect("sid") + ns.server.disconnect.assert_awaited_with("sid", namespace="/foo") + 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 + result["result"] = reason - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_client(mock.MagicMock()) - await ns.trigger_event('disconnect', 'foo') - assert result['result'] == 'foo' + 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' + result["result"] = "ok" - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_client(mock.MagicMock()) - await ns.trigger_event('disconnect', 'foo') - assert result['result'] == 'ok' + 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' + result["result"] = "ok" - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_client(mock.MagicMock()) - await ns.trigger_event('disconnect', 'foo') - assert result['result'] == 'ok' + await ns.trigger_event("disconnect", "foo") + assert result["result"] == "ok" async def test_sync_event_client(self): result = {} class MyNamespace(async_namespace.AsyncClientNamespace): def on_custom_message(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_client(mock.MagicMock()) - await ns.trigger_event('custom_message', 'sid', {'data': 'data'}) - assert result['result'] == ('sid', {'data': 'data'}) + await ns.trigger_event("custom_message", "sid", {"data": "data"}) + assert result["result"] == ("sid", {"data": "data"}) async def test_async_event_client(self): result = {} class MyNamespace(async_namespace.AsyncClientNamespace): async def on_custom_message(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_client(mock.MagicMock()) - await ns.trigger_event('custom_message', 'sid', {'data': 'data'}) - assert result['result'] == ('sid', {'data': 'data'}) + await ns.trigger_event("custom_message", "sid", {"data": "data"}) + assert result["result"] == ("sid", {"data": "data"}) async def test_event_not_found_client(self): result = {} class MyNamespace(async_namespace.AsyncClientNamespace): async def on_custom_message(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_client(mock.MagicMock()) - await ns.trigger_event('another_custom_message', 'sid', - {'data': 'data'}) + await ns.trigger_event("another_custom_message", "sid", {"data": "data"}) assert result == {} async def test_emit_client(self): - ns = async_namespace.AsyncClientNamespace('/foo') + ns = async_namespace.AsyncClientNamespace("/foo") mock_client = mock.MagicMock() mock_client.emit = mock.AsyncMock() ns._set_client(mock_client) - await 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' + "ev", data="data", namespace="/foo", callback="cb" ) - await 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' + "ev", data="data", namespace="/bar", callback="cb" ) async def test_send_client(self): - ns = async_namespace.AsyncClientNamespace('/foo') + ns = async_namespace.AsyncClientNamespace("/foo") mock_client = mock.MagicMock() mock_client.send = mock.AsyncMock() ns._set_client(mock_client) - await ns.send(data='data', callback='cb') - ns.client.send.assert_awaited_with( - 'data', namespace='/foo', callback='cb' - ) - await ns.send(data='data', namespace='/bar', callback='cb') - ns.client.send.assert_awaited_with( - 'data', namespace='/bar', callback='cb' - ) + await ns.send(data="data", callback="cb") + ns.client.send.assert_awaited_with("data", namespace="/foo", callback="cb") + await ns.send(data="data", namespace="/bar", callback="cb") + ns.client.send.assert_awaited_with("data", namespace="/bar", callback="cb") async def test_call_client(self): - ns = async_namespace.AsyncClientNamespace('/foo') + ns = async_namespace.AsyncClientNamespace("/foo") mock_client = mock.MagicMock() mock_client.call = mock.AsyncMock() ns._set_client(mock_client) - await ns.call('ev', data='data') + await ns.call("ev", data="data") ns.client.call.assert_awaited_with( - 'ev', data='data', namespace='/foo', timeout=None + "ev", data="data", namespace="/foo", timeout=None ) - await 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 + "ev", data="data", namespace="/bar", timeout=45 ) async def test_disconnect_client(self): - ns = async_namespace.AsyncClientNamespace('/foo') + ns = async_namespace.AsyncClientNamespace("/foo") mock_client = mock.MagicMock() mock_client.disconnect = mock.AsyncMock() ns._set_client(mock_client) diff --git a/tests/async/test_pubsub_manager.py b/tests/async/test_pubsub_manager.py index 71d948a..c9edf00 100644 --- a/tests/async/test_pubsub_manager.py +++ b/tests/async/test_pubsub_manager.py @@ -4,9 +4,7 @@ from unittest import mock import pytest -from socketio import async_manager -from socketio import async_pubsub_manager -from socketio import packet +from socketio import async_manager, async_pubsub_manager, packet class TestAsyncPubSubManager: @@ -27,18 +25,16 @@ class TestAsyncPubSubManager: self.pm = async_pubsub_manager.AsyncPubSubManager() self.pm._publish = mock.AsyncMock() self.pm.set_server(mock_server) - self.pm.host_id = '123456' + self.pm.host_id = "123456" self.pm.initialize() async def test_default_init(self): - assert self.pm.channel == 'socketio' - self.pm.server.start_background_task.assert_called_once_with( - self.pm._thread - ) + assert self.pm.channel == "socketio" + self.pm.server.start_background_task.assert_called_once_with(self.pm._thread) async def test_custom_init(self): - pubsub = async_pubsub_manager.AsyncPubSubManager(channel='foo') - assert pubsub.channel == 'foo' + pubsub = async_pubsub_manager.AsyncPubSubManager(channel="foo") + assert pubsub.channel == "foo" assert len(pubsub.host_id) == 32 async def test_write_only_init(self): @@ -46,194 +42,211 @@ class TestAsyncPubSubManager: pm = async_pubsub_manager.AsyncPubSubManager(write_only=True) pm.set_server(mock_server) pm.initialize() - assert pm.channel == 'socketio' + assert pm.channel == "socketio" assert len(pm.host_id) == 32 assert pm.server.start_background_task.call_count == 0 async def test_emit(self): - await self.pm.emit('foo', 'bar') + await self.pm.emit("foo", "bar") self.pm._publish.assert_awaited_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/', - 'room': None, - 'skip_sid': None, - 'callback': None, - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "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) + sid = "room-mate" + await self.pm.emit("foo", "bar", to=sid) self.pm._publish.assert_awaited_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/', - 'room': sid, - 'skip_sid': None, - 'callback': None, - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "namespace": "/", + "room": sid, + "skip_sid": None, + "callback": None, + "host_id": "123456", } ) async def test_emit_with_namespace(self): - await self.pm.emit('foo', 'bar', namespace='/baz') + await self.pm.emit("foo", "bar", namespace="/baz") self.pm._publish.assert_awaited_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/baz', - 'room': None, - 'skip_sid': None, - 'callback': None, - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "namespace": "/baz", + "room": None, + "skip_sid": None, + "callback": None, + "host_id": "123456", } ) async def test_emit_with_room(self): - await self.pm.emit('foo', 'bar', room='baz') + await self.pm.emit("foo", "bar", room="baz") self.pm._publish.assert_awaited_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/', - 'room': 'baz', - 'skip_sid': None, - 'callback': None, - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "namespace": "/", + "room": "baz", + "skip_sid": None, + "callback": None, + "host_id": "123456", } ) async def test_emit_with_skip_sid(self): - await self.pm.emit('foo', 'bar', skip_sid='baz') + await self.pm.emit("foo", "bar", skip_sid="baz") self.pm._publish.assert_awaited_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/', - 'room': None, - 'skip_sid': 'baz', - 'callback': None, - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "namespace": "/", + "room": None, + "skip_sid": "baz", + "callback": None, + "host_id": "123456", } ) async def test_emit_with_callback(self): - with mock.patch.object( - self.pm, '_generate_ack_id', return_value='123' - ): - await self.pm.emit('foo', 'bar', room='baz', callback='cb') + with mock.patch.object(self.pm, "_generate_ack_id", return_value="123"): + await self.pm.emit("foo", "bar", room="baz", callback="cb") self.pm._publish.assert_awaited_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/', - 'room': 'baz', - 'skip_sid': None, - 'callback': ('baz', '/', '123'), - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "namespace": "/", + "room": "baz", + "skip_sid": None, + "callback": ("baz", "/", "123"), + "host_id": "123456", } ) async def test_emit_with_callback_without_server(self): standalone_pm = async_pubsub_manager.AsyncPubSubManager() with pytest.raises(RuntimeError): - await standalone_pm.emit('foo', 'bar', callback='cb') + await standalone_pm.emit("foo", "bar", callback="cb") async def test_emit_with_callback_missing_room(self): - with mock.patch.object( - self.pm, '_generate_ack_id', return_value='123' - ): + with mock.patch.object(self.pm, "_generate_ack_id", return_value="123"): with pytest.raises(ValueError): - await self.pm.emit('foo', 'bar', callback='cb') + await self.pm.emit("foo", "bar", callback="cb") 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 - ) + 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 - assert self.pm.server._send_eio_packet.await_args_list[0][0][0] \ - == '123' + assert self.pm.server._send_eio_packet.await_args_list[0][0][0] == "123" pkt = self.pm.server._send_eio_packet.await_args_list[0][0][1] assert pkt.encode() == '42["foo","bar"]' 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') + 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'} + { + "method": "disconnect", + "sid": sid, + "namespace": "/foo", + "host_id": "123456", + } ) async def test_disconnect(self): - await self.pm.disconnect('foo', '/') + await self.pm.disconnect("foo", "/") self.pm._publish.assert_awaited_once_with( - {'method': 'disconnect', 'sid': 'foo', 'namespace': '/', - 'host_id': '123456'} + { + "method": "disconnect", + "sid": "foo", + "namespace": "/", + "host_id": "123456", + } ) async def test_disconnect_ignore_queue(self): - sid = await self.pm.connect('123', '/') - self.pm.pre_disconnect(sid, '/') - await self.pm.disconnect(sid, '/', ignore_queue=True) + sid = await self.pm.connect("123", "/") + self.pm.pre_disconnect(sid, "/") + await self.pm.disconnect(sid, "/", ignore_queue=True) self.pm._publish.assert_not_awaited() - assert self.pm.is_connected(sid, '/') is False + assert self.pm.is_connected(sid, "/") is False 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' + 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( - {'method': 'enter_room', 'sid': '456', 'room': 'foo', - 'namespace': '/', 'host_id': '123456'} + { + "method": "enter_room", + "sid": "456", + "room": "foo", + "namespace": "/", + "host_id": "123456", + } ) 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['/'] + 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'} + { + "method": "leave_room", + "sid": "456", + "room": "foo", + "namespace": "/", + "host_id": "123456", + } ) async def test_close_room(self): - await self.pm.close_room('foo') + await self.pm.close_room("foo") self.pm._publish.assert_awaited_once_with( - {'method': 'close_room', 'room': 'foo', 'namespace': '/', - 'host_id': '123456'} + { + "method": "close_room", + "room": "foo", + "namespace": "/", + "host_id": "123456", + } ) async def test_close_room_with_namespace(self): - await self.pm.close_room('foo', '/bar') + await self.pm.close_room("foo", "/bar") self.pm._publish.assert_awaited_once_with( - {'method': 'close_room', 'room': 'foo', 'namespace': '/bar', - 'host_id': '123456'} + { + "method": "close_room", + "room": "foo", + "namespace": "/bar", + "host_id": "123456", + } ) 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'}) + 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', + "foo", + "bar", namespace=None, room=None, skip_sid=None, @@ -241,236 +254,202 @@ class TestAsyncPubSubManager: ) async def test_handle_emit_with_namespace(self): - with mock.patch.object( - async_manager.AsyncManager, 'emit' - ) as super_emit: + with mock.patch.object(async_manager.AsyncManager, "emit") as super_emit: await self.pm._handle_emit( - {'event': 'foo', 'data': 'bar', 'namespace': '/baz'} + {"event": "foo", "data": "bar", "namespace": "/baz"} ) super_emit.assert_awaited_once_with( - 'foo', - 'bar', - namespace='/baz', + "foo", + "bar", + namespace="/baz", room=None, skip_sid=None, callback=None, ) async def test_handle_emit_with_room(self): - with mock.patch.object( - async_manager.AsyncManager, 'emit' - ) as super_emit: - await self.pm._handle_emit( - {'event': 'foo', 'data': 'bar', 'room': 'baz'} - ) + with mock.patch.object(async_manager.AsyncManager, "emit") as super_emit: + await self.pm._handle_emit({"event": "foo", "data": "bar", "room": "baz"}) super_emit.assert_awaited_once_with( - 'foo', - 'bar', + "foo", + "bar", namespace=None, - room='baz', + room="baz", skip_sid=None, callback=None, ) async def test_handle_emit_with_skip_sid(self): - with mock.patch.object( - async_manager.AsyncManager, 'emit' - ) as super_emit: + with mock.patch.object(async_manager.AsyncManager, "emit") as super_emit: await self.pm._handle_emit( - {'event': 'foo', 'data': 'bar', 'skip_sid': '123'} + {"event": "foo", "data": "bar", "skip_sid": "123"} ) super_emit.assert_awaited_once_with( - 'foo', - 'bar', + "foo", + "bar", namespace=None, room=None, - skip_sid='123', + skip_sid="123", callback=None, ) async def test_handle_emit_with_remote_callback(self): - with mock.patch.object( - async_manager.AsyncManager, 'emit' - ) as super_emit: + with mock.patch.object(async_manager.AsyncManager, "emit") as super_emit: await self.pm._handle_emit( { - 'event': 'foo', - 'data': 'bar', - 'namespace': '/baz', - 'callback': ('sid', '/baz', 123), - 'host_id': 'x', + "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') - 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.await_args[1]['callback'], functools.partial - ) - await super_emit.await_args[1]['callback']('one', 2, 'three') + 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.await_args[1]["callback"], functools.partial) + await super_emit.await_args[1]["callback"]("one", 2, "three") self.pm._publish.assert_awaited_once_with( { - 'method': 'callback', - 'host_id': 'x', - 'sid': 'sid', - 'namespace': '/baz', - 'id': 123, - 'args': ('one', 2, 'three'), + "method": "callback", + "host_id": "x", + "sid": "sid", + "namespace": "/baz", + "id": 123, + "args": ("one", 2, "three"), } ) async def test_handle_emit_with_local_callback(self): - with mock.patch.object( - async_manager.AsyncManager, 'emit' - ) as super_emit: + with mock.patch.object(async_manager.AsyncManager, "emit") as super_emit: await self.pm._handle_emit( { - 'event': 'foo', - 'data': 'bar', - 'namespace': '/baz', - 'callback': ('sid', '/baz', 123), - 'host_id': self.pm.host_id, + "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') - 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.await_args[1]['callback'], functools.partial - ) - await super_emit.await_args[1]['callback']('one', 2, 'three') + 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.await_args[1]["callback"], functools.partial) + await super_emit.await_args[1]["callback"]("one", 2, "three") self.pm._publish.assert_not_awaited() async def test_handle_callback(self): host_id = self.pm.host_id - with mock.patch.object( - self.pm, 'trigger_callback' - ) as trigger: + with mock.patch.object(self.pm, "trigger_callback") as trigger: await self.pm._handle_callback( { - 'method': 'callback', - 'host_id': host_id, - 'sid': 'sid', - 'namespace': '/', - 'id': 123, - 'args': ('one', 2), + "method": "callback", + "host_id": host_id, + "sid": "sid", + "namespace": "/", + "id": 123, + "args": ("one", 2), } ) - trigger.assert_awaited_once_with('sid', 123, ('one', 2)) + trigger.assert_awaited_once_with("sid", 123, ("one", 2)) async def test_handle_callback_bad_host_id(self): - with mock.patch.object( - self.pm, 'trigger_callback' - ) as trigger: + with mock.patch.object(self.pm, "trigger_callback") as trigger: await self.pm._handle_callback( { - 'method': 'callback', - 'host_id': 'bad', - 'sid': 'sid', - 'namespace': '/', - 'id': 123, - 'args': ('one', 2), + "method": "callback", + "host_id": "bad", + "sid": "sid", + "namespace": "/", + "id": 123, + "args": ("one", 2), } ) assert trigger.await_count == 0 async def test_handle_callback_missing_args(self): host_id = self.pm.host_id - with mock.patch.object( - self.pm, 'trigger_callback' - ) as trigger: + with mock.patch.object(self.pm, "trigger_callback") as trigger: await self.pm._handle_callback( { - 'method': 'callback', - 'host_id': host_id, - 'sid': 'sid', - 'namespace': '/', - 'id': 123, + "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': '/', + "method": "callback", + "host_id": host_id, + "sid": "sid", + "namespace": "/", } ) await self.pm._handle_callback( - {'method': 'callback', 'host_id': host_id, 'sid': 'sid'} - ) - await self.pm._handle_callback( - {'method': 'callback', 'host_id': host_id} + {"method": "callback", "host_id": host_id, "sid": "sid"} ) + await self.pm._handle_callback({"method": "callback", "host_id": host_id}) assert trigger.await_count == 0 async def test_handle_disconnect(self): await self.pm._handle_disconnect( - {'method': 'disconnect', 'sid': '123', 'namespace': '/foo'} + {"method": "disconnect", "sid": "123", "namespace": "/foo"} ) self.pm.server.disconnect.assert_awaited_once_with( - sid='123', namespace='/foo', ignore_queue=True + sid="123", namespace="/foo", ignore_queue=True ) async def test_handle_enter_room(self): - sid = await self.pm.connect('123', '/') + sid = await self.pm.connect("123", "/") with mock.patch.object( - async_manager.AsyncManager, 'enter_room' + async_manager.AsyncManager, "enter_room" ) as super_enter_room: await self.pm._handle_enter_room( - {'method': 'enter_room', 'sid': sid, 'namespace': '/', - 'room': 'foo'} + {"method": "enter_room", "sid": sid, "namespace": "/", "room": "foo"} ) await self.pm._handle_enter_room( - {'method': 'enter_room', 'sid': '456', 'namespace': '/', - 'room': 'foo'} + {"method": "enter_room", "sid": "456", "namespace": "/", "room": "foo"} ) - super_enter_room.assert_awaited_once_with(sid, '/', 'foo') + super_enter_room.assert_awaited_once_with(sid, "/", "foo") async def test_handle_leave_room(self): - sid = await self.pm.connect('123', '/') + sid = await self.pm.connect("123", "/") with mock.patch.object( - async_manager.AsyncManager, 'leave_room' + async_manager.AsyncManager, "leave_room" ) as super_leave_room: await self.pm._handle_leave_room( - {'method': 'leave_room', 'sid': sid, 'namespace': '/', - 'room': 'foo'} + {"method": "leave_room", "sid": sid, "namespace": "/", "room": "foo"} ) await self.pm._handle_leave_room( - {'method': 'leave_room', 'sid': '456', 'namespace': '/', - 'room': 'foo'} + {"method": "leave_room", "sid": "456", "namespace": "/", "room": "foo"} ) - super_leave_room.assert_awaited_once_with(sid, '/', 'foo') + super_leave_room.assert_awaited_once_with(sid, "/", "foo") async def test_handle_close_room(self): with mock.patch.object( - async_manager.AsyncManager, 'close_room' + async_manager.AsyncManager, "close_room" ) as super_close_room: - await self.pm._handle_close_room( - {'method': 'close_room', 'room': 'foo'} - ) - super_close_room.assert_awaited_once_with( - room='foo', namespace=None - ) + await self.pm._handle_close_room({"method": "close_room", "room": "foo"}) + super_close_room.assert_awaited_once_with(room="foo", namespace=None) async def test_handle_close_room_with_namespace(self): with mock.patch.object( - async_manager.AsyncManager, 'close_room' + async_manager.AsyncManager, "close_room" ) as super_close_room: await self.pm._handle_close_room( { - 'method': 'close_room', - 'room': 'foo', - 'namespace': '/bar', + "method": "close_room", + "room": "foo", + "namespace": "/bar", } ) - super_close_room.assert_awaited_once_with( - room='foo', namespace='/bar' - ) + super_close_room.assert_awaited_once_with(room="foo", namespace="/bar") async def test_background_thread(self): self.pm._handle_emit = mock.AsyncMock() @@ -484,72 +463,100 @@ class TestAsyncPubSubManager: async def messages(): import pickle - yield {'method': 'emit', 'value': 'foo', 'host_id': 'x'} - yield {'missing': 'method', 'host_id': 'x'} + 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 {'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 { + "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 { + "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" # these should not publish anything on the queue, as they come from # the same host - yield {'method': 'emit', 'value': 'foo', 'host_id': host_id} - 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 {"method": "emit", "value": "foo", "host_id": host_id} + 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} + ) self.pm._listen = messages await self.pm._thread() self.pm._handle_emit.assert_awaited_once_with( - {'method': 'emit', 'value': 'foo', 'host_id': 'x'} + {"method": "emit", "value": "foo", "host_id": "x"} ) self.pm._handle_callback.assert_any_await( - {'method': 'callback', 'value': 'bar', 'host_id': 'x'} + {"method": "callback", "value": "bar", "host_id": "x"} ) self.pm._handle_callback.assert_any_await( - {'method': 'callback', 'value': 'bar', 'host_id': host_id} + {"method": "callback", "value": "bar", "host_id": host_id} ) self.pm._handle_disconnect.assert_awaited_once_with( - {'method': 'disconnect', 'sid': '123', 'namespace': '/foo', - 'host_id': 'x'} + {"method": "disconnect", "sid": "123", "namespace": "/foo", "host_id": "x"} ) self.pm._handle_enter_room.assert_awaited_once_with( - {'method': 'enter_room', 'sid': '123', 'namespace': '/foo', - 'room': 'room', 'host_id': 'x'} + { + "method": "enter_room", + "sid": "123", + "namespace": "/foo", + "room": "room", + "host_id": "x", + } ) self.pm._handle_leave_room.assert_awaited_once_with( - {'method': 'leave_room', 'sid': '123', 'namespace': '/foo', - 'room': 'room', 'host_id': 'x'} + { + "method": "leave_room", + "sid": "123", + "namespace": "/foo", + "room": "room", + "host_id": "x", + } ) self.pm._handle_close_room.assert_awaited_once_with( - {'method': 'close_room', 'value': 'baz', 'host_id': 'x'} + {"method": "close_room", "value": "baz", "host_id": "x"} ) async def test_background_thread_exception(self): - self.pm._handle_emit = mock.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'} - yield {'method': 'emit', 'value': 'bar', 'host_id': 'x'} + yield {"method": "emit", "value": "foo", "host_id": "x"} + yield {"method": "emit", "value": "bar", "host_id": "x"} self.pm._listen = messages await self.pm._thread() self.pm._handle_emit.assert_any_await( - {'method': 'emit', 'value': 'foo', 'host_id': 'x'} + {"method": "emit", "value": "foo", "host_id": "x"} ) self.pm._handle_emit.assert_awaited_with( - {'method': 'emit', 'value': 'bar', 'host_id': 'x'} + {"method": "emit", "value": "bar", "host_id": "x"} ) diff --git a/tests/async/test_server.py b/tests/async/test_server.py index f60de27..38aad94 100644 --- a/tests/async/test_server.py +++ b/tests/async/test_server.py @@ -2,20 +2,19 @@ import asyncio import logging from unittest import mock -from engineio import json -from engineio import packet as eio_packet import pytest +from engineio import json, packet as eio_packet -from socketio import async_server -from socketio import async_namespace -from socketio import exceptions -from socketio import namespace -from socketio import packet +from socketio import async_namespace, async_server, exceptions, namespace, packet -@mock.patch('socketio.server.engineio.AsyncServer', **{ - 'return_value.generate_id.side_effect': [str(i) for i in range(1, 10)], - 'return_value.send_packet': mock.AsyncMock()}) +@mock.patch( + "socketio.server.engineio.AsyncServer", + **{ + "return_value.generate_id.side_effect": [str(i) for i in range(1, 10)], + "return_value.send_packet": mock.AsyncMock(), + }, +) class TestAsyncServer: def teardown_method(self): # restore JSON encoder, in case a test changed it @@ -34,74 +33,72 @@ class TestAsyncServer: 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' - ) + s = async_server.AsyncServer(client_manager=mgr, async_handlers=True, foo="bar") await s.handle_request({}) await s.handle_request({}) - eio.assert_called_once_with(**{'foo': 'bar', 'async_handlers': False}) + 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 async def test_attach(self, eio): s = async_server.AsyncServer() - s.attach('app', 'path') - eio.return_value.attach.assert_called_once_with('app', 'path') + s.attach("app", "path") + eio.return_value.attach.assert_called_once_with("app", "path") async def test_on_event(self, eio): s = async_server.AsyncServer() - @s.on('connect') + @s.on("connect") def foo(): pass def bar(reason): pass - s.on('disconnect', bar) - s.on('disconnect', bar, namespace='/foo') + s.on("disconnect", bar) + s.on("disconnect", bar, namespace="/foo") - assert s.handlers['/']['connect'] == foo - assert s.handlers['/']['disconnect'] == bar - assert s.handlers['/foo']['disconnect'] == bar + assert s.handlers["/"]["connect"] == foo + assert s.handlers["/"]["disconnect"] == bar + assert s.handlers["/foo"]["disconnect"] == bar async def test_emit(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) await s.emit( - 'my event', - {'foo': 'bar'}, - to='room', - skip_sid='123', - namespace='/foo', - callback='cb', + "my event", + {"foo": "bar"}, + to="room", + skip_sid="123", + namespace="/foo", + callback="cb", ) s.manager.emit.assert_awaited_once_with( - 'my event', - {'foo': 'bar'}, - '/foo', - room='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + "/foo", + room="room", + skip_sid="123", + callback="cb", ignore_queue=False, ) await s.emit( - 'my event', - {'foo': 'bar'}, - room='room', - skip_sid='123', - namespace='/foo', - callback='cb', + "my event", + {"foo": "bar"}, + room="room", + skip_sid="123", + namespace="/foo", + callback="cb", ignore_queue=True, ) s.manager.emit.assert_awaited_with( - 'my event', - {'foo': 'bar'}, - '/foo', - room='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + "/foo", + room="room", + skip_sid="123", + callback="cb", ignore_queue=True, ) @@ -109,36 +106,36 @@ class TestAsyncServer: mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) await s.emit( - 'my event', - {'foo': 'bar'}, - to='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + to="room", + skip_sid="123", + callback="cb", ) s.manager.emit.assert_awaited_once_with( - 'my event', - {'foo': 'bar'}, - '/', - room='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + "/", + room="room", + skip_sid="123", + callback="cb", ignore_queue=False, ) await s.emit( - 'my event', - {'foo': 'bar'}, - room='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + room="room", + skip_sid="123", + callback="cb", ignore_queue=True, ) s.manager.emit.assert_awaited_with( - 'my event', - {'foo': 'bar'}, - '/', - room='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + "/", + room="room", + skip_sid="123", + callback="cb", ignore_queue=True, ) @@ -146,36 +143,36 @@ class TestAsyncServer: mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) await s.send( - 'foo', - to='room', - skip_sid='123', - namespace='/foo', - callback='cb', + "foo", + to="room", + skip_sid="123", + namespace="/foo", + callback="cb", ) s.manager.emit.assert_awaited_once_with( - 'message', - 'foo', - '/foo', - room='room', - skip_sid='123', - callback='cb', + "message", + "foo", + "/foo", + room="room", + skip_sid="123", + callback="cb", ignore_queue=False, ) await s.send( - 'foo', - room='room', - skip_sid='123', - namespace='/foo', - callback='cb', + "foo", + room="room", + skip_sid="123", + namespace="/foo", + callback="cb", ignore_queue=True, ) s.manager.emit.assert_awaited_with( - 'message', - 'foo', - '/foo', - room='room', - skip_sid='123', - callback='cb', + "message", + "foo", + "/foo", + room="room", + skip_sid="123", + callback="cb", ignore_queue=True, ) @@ -184,11 +181,11 @@ class TestAsyncServer: s = async_server.AsyncServer(client_manager=mgr) async def fake_event_wait(): - s.manager.emit.await_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 - assert await s.call('foo', sid='123') == ('foo', 321) + assert await s.call("foo", sid="123") == ("foo", 321) async def test_call_with_timeout(self, eio): mgr = self._get_mock_manager() @@ -199,116 +196,113 @@ class TestAsyncServer: s.eio.create_event.return_value.wait = fake_event_wait with pytest.raises(exceptions.TimeoutError): - await s.call('foo', sid='123', timeout=0.01) + await s.call("foo", sid="123", timeout=0.01) async def test_call_with_broadcast(self, eio): s = async_server.AsyncServer() with pytest.raises(ValueError): - await s.call('foo') + await s.call("foo") async def test_call_without_async_handlers(self, eio): mgr = self._get_mock_manager() - s = async_server.AsyncServer( - client_manager=mgr, async_handlers=False - ) + s = async_server.AsyncServer(client_manager=mgr, async_handlers=False) with pytest.raises(RuntimeError): - await s.call('foo', sid='123', timeout=12) + await s.call("foo", sid="123", timeout=12) async def test_enter_room(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - await s.enter_room('123', 'room', namespace='/foo') - s.manager.enter_room.assert_awaited_once_with('123', '/foo', 'room') + await s.enter_room("123", "room", namespace="/foo") + s.manager.enter_room.assert_awaited_once_with("123", "/foo", "room") async def test_enter_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - await s.enter_room('123', 'room') - s.manager.enter_room.assert_awaited_once_with('123', '/', 'room') + await s.enter_room("123", "room") + s.manager.enter_room.assert_awaited_once_with("123", "/", "room") async def test_leave_room(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - await s.leave_room('123', 'room', namespace='/foo') - s.manager.leave_room.assert_awaited_once_with('123', '/foo', 'room') + await s.leave_room("123", "room", namespace="/foo") + s.manager.leave_room.assert_awaited_once_with("123", "/foo", "room") async def test_leave_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - await s.leave_room('123', 'room') - s.manager.leave_room.assert_awaited_once_with('123', '/', 'room') + await s.leave_room("123", "room") + s.manager.leave_room.assert_awaited_once_with("123", "/", "room") async def test_close_room(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - await s.close_room('room', namespace='/foo') - s.manager.close_room.assert_awaited_once_with('room', '/foo') + await s.close_room("room", namespace="/foo") + s.manager.close_room.assert_awaited_once_with("room", "/foo") async def test_close_room_default_namespace(self, eio): mgr = self._get_mock_manager() s = async_server.AsyncServer(client_manager=mgr) - await s.close_room('room') - s.manager.close_room.assert_awaited_once_with('room', '/') + await s.close_room("room") + s.manager.close_room.assert_awaited_once_with("room", "/") 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') + s.rooms("123", namespace="/foo") + s.manager.get_rooms.assert_called_once_with("123", "/foo") 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', '/') + s.rooms("123") + s.manager.get_rooms.assert_called_once_with("123", "/") async def test_handle_request(self, eio): eio.return_value.handle_request = mock.AsyncMock() s = async_server.AsyncServer() - await s.handle_request('environ') - s.eio.handle_request.assert_awaited_once_with('environ') + await s.handle_request("environ") + s.eio.handle_request.assert_awaited_once_with("environ") async def test_send_packet(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - 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"]' + 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"]') async def test_send_eio_packet(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - await 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' + 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' + assert pkt.encode() == "4hello" 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 = await s.manager.connect('123', '/foo') - assert s.transport(sid_foo, '/foo') == 'polling' - s.eio.transport.assert_called_once_with('123') + s.eio.transport = mock.MagicMock(return_value="polling") + sid_foo = await s.manager.connect("123", "/foo") + assert s.transport(sid_foo, "/foo") == "polling" + s.eio.transport.assert_called_once_with("123") 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) - 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"}') + s.on("connect", handler) + 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 - await s._handle_eio_connect('456', 'environ') - await 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 async def test_handle_connect_with_auth(self, eio): @@ -316,15 +310,15 @@ class TestAsyncServer: s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock() - s.on('connect', handler) - 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"}') + s.on("connect", handler) + 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 - await s._handle_eio_connect('456', 'environ') - await 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 async def test_handle_connect_with_auth_none(self, eio): @@ -332,15 +326,15 @@ class TestAsyncServer: s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock(side_effect=[TypeError, None, None]) - s.on('connect', handler) - 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"}') + s.on("connect", handler) + 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 - await s._handle_eio_connect('456', 'environ') - await 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 async def test_handle_connect_async(self, eio): @@ -348,199 +342,198 @@ class TestAsyncServer: s = async_server.AsyncServer() s.manager.initialize = mock.MagicMock() handler = mock.AsyncMock() - s.on('connect', handler) - 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"}') + s.on("connect", handler) + 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 - await s._handle_eio_connect('456', 'environ') - await 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 async def test_handle_connect_with_default_implied_namespaces(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - 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') + 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") async def test_handle_connect_with_implied_namespaces(self, eio): eio.return_value.send = mock.AsyncMock() - s = async_server.AsyncServer(namespaces=['/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') + s = async_server.AsyncServer(namespaces=["/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") async def test_handle_connect_with_all_implied_namespaces(self, eio): eio.return_value.send = mock.AsyncMock() - s = async_server.AsyncServer(namespaces='*') - 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') + s = async_server.AsyncServer(namespaces="*") + 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") 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') - 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"}') + s.on("connect", handler, namespace="/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"}') 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) - 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"}') + s.on("connect", handler) + 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 - await s._handle_eio_connect('456', 'environ') - await 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 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) - 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.on("connect", handler) + 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'} + "123", '4{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} 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') - 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.on("connect", handler, namespace="/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'} + "123", '4/foo,{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} 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) - 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"}') + 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_any_await("123", '0{"sid":"1"}') s.eio.send.assert_any_await( - '123', '1{"message":"Connection rejected by server"}') - assert s.environ == {'123': 'environ'} + "123", '1{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} 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') - 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"}') + s.on("connect", handler, namespace="/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"}') s.eio.send.assert_any_await( - '123', '1/foo,{"message":"Connection rejected by server"}') - assert s.environ == {'123': 'environ'} + "123", '1/foo,{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} 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') + side_effect=exceptions.ConnectionRefusedError("fail_reason") ) - 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":"fail_reason"}') - assert s.environ == {'123': 'environ'} + 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":"fail_reason"}') + 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() - handler = mock.MagicMock( - side_effect=exceptions.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') + handler = mock.MagicMock(side_effect=exceptions.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 rejected by server"}') - assert s.environ == {'123': 'environ'} + "123", '4{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} async def test_handle_connect_namespace_rejected_with_exception(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() handler = mock.MagicMock( - side_effect=exceptions.ConnectionRefusedError( - 'fail_reason', 1, '2') + side_effect=exceptions.ConnectionRefusedError("fail_reason", 1, "2") ) - s.on('connect', handler, namespace='/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.on("connect", handler, namespace="/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'} + "123", '4/foo,{"message":"fail_reason","data":[1,"2"]}' + ) + assert s.environ == {"123": "environ"} - async 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') - 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') + handler = mock.MagicMock(side_effect=exceptions.ConnectionRefusedError()) + s.on("connect", handler, namespace="/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'} + "123", '4/foo,{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} 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) - await s._handle_eio_connect('123', 'environ') - await s._handle_eio_message('123', '0') - 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) + 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_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): @@ -548,13 +541,12 @@ class TestAsyncServer: 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) + 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): @@ -562,290 +554,283 @@ class TestAsyncServer: 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) + 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 == {} 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) + s.on("disconnect", handler) handler_namespace = mock.MagicMock() - 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', 'foo') + 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", "foo") handler.assert_not_called() - handler_namespace.assert_called_once_with('1', 'foo') + handler_namespace.assert_called_once_with("1", "foo") assert s.environ == {} 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) + s.on("disconnect", handler) handler_namespace = mock.MagicMock() - 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_message('123', '1/foo,') + 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_message("123", "1/foo,") assert handler.call_count == 0 - handler_namespace.assert_called_once_with( - '1', s.reason.CLIENT_DISCONNECT) - assert s.environ == {'123': 'environ'} + 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', 'foo') + await s._handle_eio_disconnect("123", "foo") async def test_handle_event(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = await 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) - 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') + s.on("msg", handler) + s.on("*", catchall_handler) + 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") async def test_handle_event_with_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = await 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') - 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') + s.on("msg", handler, namespace="/foo") + s.on("*", catchall_handler, namespace="/foo") + 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") 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 = await s.manager.connect('123', '/foo') - sid_bar = await 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() star_foo_handler = mock.MagicMock() star_star_handler = mock.MagicMock() - s.on('connect', connect_star_handler, namespace='*') - s.on('msg', msg_foo_handler, namespace='/foo') - s.on('msg', msg_star_handler, namespace='*') - s.on('*', star_foo_handler, namespace='/foo') - s.on('*', star_star_handler, namespace='*') - 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, - 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') - star_foo_handler.assert_called_once_with( - 'my message', sid_foo, 'a', 'b', 'c') + s.on("connect", connect_star_handler, namespace="*") + s.on("msg", msg_foo_handler, namespace="/foo") + s.on("msg", msg_star_handler, namespace="*") + s.on("*", star_foo_handler, namespace="/foo") + s.on("*", star_star_handler, namespace="*") + 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, 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") + star_foo_handler.assert_called_once_with("my message", sid_foo, "a", "b", "c") star_star_handler.assert_called_once_with( - 'my message', '/bar', sid_bar, 'a', 'b', 'c') + "my message", "/bar", sid_bar, "a", "b", "c" + ) async def test_handle_event_with_disconnected_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - await s.manager.connect('123', '/foo') + await s.manager.connect("123", "/foo") handler = mock.MagicMock() - s.on('my message', handler, namespace='/bar') - await s._handle_eio_message('123', '2/bar,["my message","a","b","c"]') + s.on("my message", handler, namespace="/bar") + await s._handle_eio_message("123", '2/bar,["my message","a","b","c"]') handler.assert_not_called() async def test_handle_event_binary(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = await s.manager.connect('123', '/') + sid = await s.manager.connect("123", "/") handler = mock.MagicMock() - s.on('my message', handler) + s.on("my message", handler) await s._handle_eio_message( - '123', + "123", '52-["my message","a",' '{"_placeholder":true,"num":1},' '{"_placeholder":true,"num":0}]', ) - 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') + 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") 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 = await s.manager.connect('123', '/') + sid = await s.manager.connect("123", "/") await s._handle_eio_message( - '123', + "123", '61-321["my message","a",' '{"_placeholder":true,"num":0}]', ) - await 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'] + sid, 321, ["my message", "a", b"foo"] ) async def test_handle_event_with_ack(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = await s.manager.connect('123', '/') - handler = mock.MagicMock(return_value='foo') - s.on('my message', handler) - 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"]' - ) + sid = await s.manager.connect("123", "/") + handler = mock.MagicMock(return_value="foo") + s.on("my message", handler) + 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"]') async def test_handle_unknown_event_with_ack(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - await s.manager.connect('123', '/') - handler = mock.MagicMock(return_value='foo') - s.on('my message', handler) - await s._handle_eio_message('123', '21000["another message","foo"]') + await s.manager.connect("123", "/") + handler = mock.MagicMock(return_value="foo") + s.on("my message", handler) + await s._handle_eio_message("123", '21000["another message","foo"]') s.eio.send.assert_not_awaited() async def test_handle_event_with_ack_none(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = await s.manager.connect('123', '/') + sid = await s.manager.connect("123", "/") handler = mock.MagicMock(return_value=None) - s.on('my message', handler) - 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[]') + s.on("my message", handler) + 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[]") async def test_handle_event_with_ack_tuple(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = await s.manager.connect('123', '/') - handler = mock.MagicMock(return_value=(1, '2', True)) - s.on('my message', handler) - 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]' - ) + sid = await s.manager.connect("123", "/") + handler = mock.MagicMock(return_value=(1, "2", True)) + s.on("my message", handler) + 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]') async def test_handle_event_with_ack_list(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = await s.manager.connect('123', '/') - handler = mock.MagicMock(return_value=[1, '2', True]) - s.on('my message', handler) - 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]]' - ) + sid = await s.manager.connect("123", "/") + handler = mock.MagicMock(return_value=[1, "2", True]) + s.on("my message", handler) + 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]]') async def test_handle_event_with_ack_binary(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer(async_handlers=False) - sid = await s.manager.connect('123', '/') - handler = mock.MagicMock(return_value=b'foo') - s.on('my message', handler) - await s._handle_eio_message('123', '21000["my message","foo"]') - handler.assert_any_call(sid, 'foo') + sid = await s.manager.connect("123", "/") + handler = mock.MagicMock(return_value=b"foo") + s.on("my message", handler) + await s._handle_eio_message("123", '21000["my message","foo"]') + handler.assert_any_call(sid, "foo") async def test_handle_error_packet(self, eio): s = async_server.AsyncServer() with pytest.raises(ValueError): - await s._handle_eio_message('123', '4') + await s._handle_eio_message("123", "4") async def test_handle_invalid_packet(self, eio): s = async_server.AsyncServer() with pytest.raises(ValueError): - await s._handle_eio_message('123', '9') + await s._handle_eio_message("123", "9") async def test_send_with_ack(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - s.handlers['/'] = {} - await s._handle_eio_connect('123', 'environ') - await s._handle_eio_message('123', '0') + s.handlers["/"] = {} + 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) - 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) + id1 = s.manager._generate_ack_id("1", cb) + id2 = s.manager._generate_ack_id("1", cb) + 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) async def test_send_with_ack_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - s.handlers['/foo'] = {} - await s._handle_eio_connect('123', 'environ') - await s._handle_eio_message('123', '0/foo,') + s.handlers["/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) + id = s.manager._generate_ack_id("1", cb) await s._send_packet( - '123', packet.Packet(packet.EVENT, ['my event', 'foo'], - namespace='/foo', id=id) + "123", + packet.Packet(packet.EVENT, ["my event", "foo"], namespace="/foo", id=id), ) - await s._handle_eio_message('123', '3/foo,1["foo",2]') - cb.assert_called_once_with('foo', 2) + await s._handle_eio_message("123", '3/foo,1["foo",2]') + cb.assert_called_once_with("foo", 2) async def test_session(self, eio): fake_session = {} async def fake_get_session(eio_sid): - assert eio_sid == '123' + assert eio_sid == "123" return fake_session async def fake_save_session(eio_sid, session): global fake_session - assert eio_sid == '123' + assert eio_sid == "123" fake_session = session eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - s.handlers['/'] = {} - s.handlers['/ns'] = {} + s.handlers["/"] = {} + s.handlers["/ns"] = {} s.eio.get_session = fake_get_session s.eio.save_session = fake_save_session async def _test(): - await s._handle_eio_connect('123', 'environ') - await s._handle_eio_message('123', '0') - await s._handle_eio_message('123', '0/ns') - sid = s.manager.sid_from_eio_sid('123', '/') - sid2 = s.manager.sid_from_eio_sid('123', '/ns') - await s.save_session(sid, {'foo': 'bar'}) + await s._handle_eio_connect("123", "environ") + await s._handle_eio_message("123", "0") + await s._handle_eio_message("123", "0/ns") + sid = s.manager.sid_from_eio_sid("123", "/") + sid2 = s.manager.sid_from_eio_sid("123", "/ns") + await s.save_session(sid, {"foo": "bar"}) async with s.session(sid) as session: - assert session == {'foo': 'bar'} - session['foo'] = 'baz' - session['bar'] = 'foo' - assert await s.get_session(sid) == {'foo': 'baz', 'bar': 'foo'} - assert fake_session == {'/': {'foo': 'baz', 'bar': 'foo'}} - async with s.session(sid2, namespace='/ns') as session: + assert session == {"foo": "bar"} + session["foo"] = "baz" + session["bar"] = "foo" + assert await s.get_session(sid) == {"foo": "baz", "bar": "foo"} + assert fake_session == {"/": {"foo": "baz", "bar": "foo"}} + async with s.session(sid2, namespace="/ns") as session: assert session == {} - session['a'] = 'b' - assert await s.get_session(sid2, namespace='/ns') == {'a': 'b'} + session["a"] = "b" + assert await s.get_session(sid2, namespace="/ns") == {"a": "b"} assert fake_session == { - '/': {'foo': 'baz', 'bar': 'foo'}, - '/ns': {'a': 'b'}, + "/": {"foo": "baz", "bar": "foo"}, + "/ns": {"a": "b"}, } await _test() @@ -854,56 +839,56 @@ class TestAsyncServer: eio.return_value.send = mock.AsyncMock() eio.return_value.disconnect = mock.AsyncMock() s = async_server.AsyncServer() - s.handlers['/'] = {} - 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', '/') + s.handlers["/"] = {} + 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", "/") 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['/'] = {} - 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', '/') + s.handlers["/"] = {} + 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", "/") 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'] = {} - 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') + s.handlers["/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") async def test_disconnect_twice(self, eio): eio.return_value.send = mock.AsyncMock() eio.return_value.disconnect = mock.AsyncMock() s = async_server.AsyncServer() - await s._handle_eio_connect('123', 'environ') - await s._handle_eio_message('123', '0') - await 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', '/') - await s.disconnect('1') + assert not s.manager.is_connected("1", "/") + await s.disconnect("1") assert calls == s.eio.send.await_count async def test_disconnect_twice_namespace(self, eio): eio.return_value.send = mock.AsyncMock() s = async_server.AsyncServer() - await s._handle_eio_connect('123', 'environ') - await s._handle_eio_message('123', '0/foo,') - await 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') - await s.disconnect('1', namespace='/foo') + assert not s.manager.is_connected("1", "/foo") + await s.disconnect("1", namespace="/foo") assert calls == s.eio.send.await_count async def test_namespace_handler(self, eio): @@ -912,34 +897,33 @@ class TestAsyncServer: class MyNamespace(async_namespace.AsyncNamespace): def on_connect(self, sid, environ): - result['result'] = (sid, environ) + result["result"] = (sid, environ) async def on_disconnect(self, sid, reason): - result['result'] = ('disconnect', sid, reason) + result["result"] = ("disconnect", sid, reason) async def on_foo(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) def on_bar(self, sid): - result['result'] = 'bar' + result["result"] = "bar" async def on_baz(self, sid, data1, data2): - result['result'] = (data1, data2) + result["result"] = (data1, data2) s = async_server.AsyncServer(async_handlers=False) - s.register_namespace(MyNamespace('/foo')) - await s._handle_eio_connect('123', 'environ') - await s._handle_eio_message('123', '0/foo,') - assert result['result'] == ('1', 'environ') - await s._handle_eio_message('123', '2/foo,["foo","a"]') - assert result['result'] == ('1', 'a') - await s._handle_eio_message('123', '2/foo,["bar"]') - assert result['result'] == 'bar' - 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', - s.reason.SERVER_DISCONNECT) + s.register_namespace(MyNamespace("/foo")) + await s._handle_eio_connect("123", "environ") + await s._handle_eio_message("123", "0/foo,") + assert result["result"] == ("1", "environ") + await s._handle_eio_message("123", '2/foo,["foo","a"]') + assert result["result"] == ("1", "a") + await s._handle_eio_message("123", '2/foo,["bar"]') + assert result["result"] == "bar" + 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", s.reason.SERVER_DISCONNECT) async def test_catchall_namespace_handler(self, eio): eio.return_value.send = mock.AsyncMock() @@ -947,33 +931,33 @@ class TestAsyncServer: class MyNamespace(async_namespace.AsyncNamespace): def on_connect(self, ns, sid, environ): - result['result'] = (sid, ns, environ) + result["result"] = (sid, ns, environ) async def on_disconnect(self, ns, sid): - result['result'] = ('disconnect', sid, ns) + result["result"] = ("disconnect", sid, ns) async def on_foo(self, ns, sid, data): - result['result'] = (sid, ns, data) + result["result"] = (sid, ns, data) def on_bar(self, ns, sid): - result['result'] = 'bar' + ns + result["result"] = "bar" + ns async def on_baz(self, ns, sid, data1, data2): - result['result'] = (ns, data1, data2) - - s = async_server.AsyncServer(async_handlers=False, namespaces='*') - s.register_namespace(MyNamespace('*')) - await s._handle_eio_connect('123', 'environ') - await s._handle_eio_message('123', '0/foo,') - assert result['result'] == ('1', '/foo', 'environ') - await s._handle_eio_message('123', '2/foo,["foo","a"]') - assert result['result'] == ('1', '/foo', 'a') - await s._handle_eio_message('123', '2/foo,["bar"]') - assert result['result'] == 'bar/foo' - await s._handle_eio_message('123', '2/foo,["baz","a","b"]') - assert result['result'] == ('/foo', 'a', 'b') - await s.disconnect('1', '/foo') - assert result['result'] == ('disconnect', '1', '/foo') + result["result"] = (ns, data1, data2) + + s = async_server.AsyncServer(async_handlers=False, namespaces="*") + s.register_namespace(MyNamespace("*")) + await s._handle_eio_connect("123", "environ") + await s._handle_eio_message("123", "0/foo,") + assert result["result"] == ("1", "/foo", "environ") + await s._handle_eio_message("123", '2/foo,["foo","a"]') + assert result["result"] == ("1", "/foo", "a") + await s._handle_eio_message("123", '2/foo,["bar"]') + assert result["result"] == "bar/foo" + await s._handle_eio_message("123", '2/foo,["baz","a","b"]') + assert result["result"] == ("/foo", "a", "b") + await s.disconnect("1", "/foo") + assert result["result"] == ("disconnect", "1", "/foo") async def test_bad_namespace_handler(self, eio): class Dummy: @@ -1004,14 +988,12 @@ class TestAsyncServer: s = async_server.AsyncServer(logger=True) assert s.logger.getEffectiveLevel() == logging.WARNING s.logger.setLevel(logging.NOTSET) - s = async_server.AsyncServer(logger='foo') - assert s.logger == 'foo' + s = async_server.AsyncServer(logger="foo") + assert s.logger == "foo" async def test_engineio_logger(self, eio): - async_server.AsyncServer(engineio_logger='foo') - eio.assert_called_once_with( - **{'logger': 'foo', 'async_handlers': False} - ) + async_server.AsyncServer(engineio_logger="foo") + eio.assert_called_once_with(**{"logger": "foo", "async_handlers": False}) async def test_custom_json(self, eio): # Warning: this test cannot run in parallel with other tests, as it @@ -1020,39 +1002,37 @@ class TestAsyncServer: class CustomJSON: @staticmethod def dumps(*args, **kwargs): - return '*** encoded ***' + return "*** encoded ***" @staticmethod def loads(*args, **kwargs): - return '+++ decoded +++' + return "+++ decoded +++" async_server.AsyncServer(json=CustomJSON) - eio.assert_called_once_with( - **{'json': CustomJSON, 'async_handlers': False} - ) + eio.assert_called_once_with(**{"json": CustomJSON, "async_handlers": False}) pkt = packet.Packet( packet_type=packet.EVENT, - data={'foo': 'bar'}, + data={"foo": "bar"}, ) - assert pkt.encode() == '2*** encoded ***' + assert pkt.encode() == "2*** encoded ***" pkt2 = packet.Packet(encoded_packet=pkt.encode()) - assert pkt2.data == '+++ decoded +++' + assert pkt2.data == "+++ decoded +++" # restore the default JSON module packet.Packet.json = json async def test_async_handlers(self, eio): s = async_server.AsyncServer(async_handlers=True) - await s.manager.connect('123', '/') - await 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, - '1', - '123', - ['my message', 'a', 'b', 'c'], - '/', + "1", + "123", + ["my message", "a", "b", "c"], + "/", None, ) @@ -1064,10 +1044,8 @@ class TestAsyncServer: 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' - ) + s.start_background_task("foo", "bar", baz="baz") + s.eio.start_background_task.assert_called_once_with("foo", "bar", baz="baz") async def test_sleep(self, eio): eio.return_value.sleep = mock.AsyncMock() diff --git a/tests/async/test_simple_client.py b/tests/async/test_simple_client.py index bfe2a90..6cbb93a 100644 --- a/tests/async/test_simple_client.py +++ b/tests/async/test_simple_client.py @@ -1,16 +1,17 @@ import asyncio from unittest import mock + import pytest from socketio import AsyncSimpleClient -from socketio.exceptions import SocketIOError, TimeoutError, DisconnectedError +from socketio.exceptions import DisconnectedError, SocketIOError, TimeoutError class TestAsyncAsyncSimpleClient: 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} + client = AsyncSimpleClient(1, "2", a="3", b=4) + assert client.client_args == (1, "2") + assert client.client_kwargs == {"a": "3", "b": 4} assert client.client is None assert client.input_buffer == [] assert not client.connected @@ -20,20 +21,32 @@ class TestAsyncAsyncSimpleClient: original_client_class = AsyncSimpleClient.client_class AsyncSimpleClient.client_class = mock_client - client = AsyncSimpleClient(123, a='b') + client = AsyncSimpleClient(123, a="b") 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') + 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') + "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' + 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 @@ -43,61 +56,71 @@ class TestAsyncAsyncSimpleClient: original_client_class = AsyncSimpleClient.client_class AsyncSimpleClient.client_class = mock_client - async with AsyncSimpleClient(123, a='b') as 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') - mock_client.assert_called_once_with(123, a='b') + 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') + "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' + 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_twice(self): - client = AsyncSimpleClient(123, a='b') + client = AsyncSimpleClient(123, a="b") client.client = mock.MagicMock() client.connected = True with pytest.raises(RuntimeError): - await client.connect('url') + await client.connect("url") async def test_properties(self): client = AsyncSimpleClient() - client.client = mock.MagicMock(transport='websocket') - client.client.get_sid.return_value = 'sid' + client.client = mock.MagicMock(transport="websocket") + client.client.get_sid.return_value = "sid" client.connected_event.set() client.connected = True - assert client.sid == 'sid' - assert client.transport == 'websocket' + assert client.sid == "sid" + assert client.transport == "websocket" async def test_emit(self): client = AsyncSimpleClient() client.client = mock.MagicMock() client.client.emit = mock.AsyncMock() - client.namespace = '/ns' + client.namespace = "/ns" client.connected_event.set() client.connected = True - await client.emit('foo', 'bar') - client.client.emit.assert_awaited_once_with('foo', 'bar', - namespace='/ns') + await client.emit("foo", "bar") + client.client.emit.assert_awaited_once_with("foo", "bar", namespace="/ns") async def test_emit_disconnected(self): client = AsyncSimpleClient() client.connected_event.set() client.connected = False with pytest.raises(DisconnectedError): - await client.emit('foo', 'bar') + await client.emit("foo", "bar") async def test_emit_retries(self): client = AsyncSimpleClient() @@ -107,28 +130,29 @@ class TestAsyncAsyncSimpleClient: client.client.emit = mock.AsyncMock() client.client.emit.side_effect = [SocketIOError(), None] - await client.emit('foo', 'bar') - client.client.emit.assert_awaited_with('foo', 'bar', namespace='/') + await client.emit("foo", "bar") + client.client.emit.assert_awaited_with("foo", "bar", namespace="/") async def test_call(self): client = AsyncSimpleClient() client.client = mock.MagicMock() client.client.call = mock.AsyncMock() - client.client.call.return_value = 'result' - client.namespace = '/ns' + client.client.call.return_value = "result" + client.namespace = "/ns" client.connected_event.set() client.connected = True - assert await 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) + "foo", "bar", namespace="/ns", timeout=60 + ) async def test_call_disconnected(self): client = AsyncSimpleClient() client.connected_event.set() client.connected = False with pytest.raises(DisconnectedError): - await client.call('foo', 'bar') + await client.call("foo", "bar") async def test_call_retries(self): client = AsyncSimpleClient() @@ -136,17 +160,16 @@ class TestAsyncAsyncSimpleClient: client.connected = True client.client = mock.MagicMock() client.client.call = mock.AsyncMock() - client.client.call.side_effect = [SocketIOError(), 'result'] + client.client.call.side_effect = [SocketIOError(), "result"] - assert await client.call('foo', 'bar') == 'result' - client.client.call.assert_awaited_with('foo', 'bar', namespace='/', - timeout=60) + assert await client.call("foo", "bar") == "result" + client.client.call.assert_awaited_with("foo", "bar", namespace="/", timeout=60) async def test_receive_with_input_buffer(self): client = AsyncSimpleClient() - client.input_buffer = ['foo', 'bar'] - assert await client.receive() == 'foo' - assert await client.receive() == 'bar' + client.input_buffer = ["foo", "bar"] + assert await client.receive() == "foo" + assert await client.receive() == "bar" async def test_receive_without_input_buffer(self): client = AsyncSimpleClient() @@ -155,11 +178,11 @@ class TestAsyncAsyncSimpleClient: client.input_event = mock.MagicMock() async def fake_wait(timeout=None): - client.input_buffer = ['foo'] + client.input_buffer = ["foo"] return True client.input_event.wait = fake_wait - assert await client.receive() == 'foo' + assert await client.receive() == "foo" async def test_receive_with_timeout(self): client = AsyncSimpleClient() diff --git a/tests/asyncio_web_server.py b/tests/asyncio_web_server.py index 8b2046c..d2056a2 100644 --- a/tests/asyncio_web_server.py +++ b/tests/asyncio_web_server.py @@ -1,7 +1,9 @@ -import requests import threading import time + +import requests import uvicorn + import socketio @@ -13,16 +15,20 @@ class SocketIOWebServer: Note 1: This class is not production-ready and is intended for testing. Note 2: This class only supports the "asgi" async_mode. """ + def __init__(self, sio, on_shutdown=None): - if sio.async_mode != 'asgi': + if sio.async_mode != "asgi": raise ValueError('The async_mode must be "asgi"') async def http_app(scope, receive, send): - await send({'type': 'http.response.start', - 'status': 200, - 'headers': [('Content-Type', 'text/plain')]}) - await send({'type': 'http.response.body', - 'body': b'OK'}) + await send( + { + "type": "http.response.start", + "status": 200, + "headers": [("Content-Type", "text/plain")], + } + ) + await send({"type": "http.response.body", "body": b"OK"}) self.sio = sio self.app = socketio.ASGIApp(sio, http_app, on_shutdown=on_shutdown) @@ -43,9 +49,9 @@ class SocketIOWebServer: # wait for the server to start while True: try: - r = requests.get(f'http://localhost:{port}/') + r = requests.get(f"http://localhost:{port}/") r.raise_for_status() - if r.text == 'OK': + if r.text == "OK": break except: time.sleep(0.1) diff --git a/tests/common/test_admin.py b/tests/common/test_admin.py index e766731..cb5b981 100644 --- a/tests/common/test_admin.py +++ b/tests/common/test_admin.py @@ -1,9 +1,11 @@ -from functools import wraps import threading import time +from functools import wraps from unittest import mock + import pytest from engineio.socket import Socket as EngineIOSocket + import socketio from socketio.exceptions import ConnectionError from tests.web_server import SocketIOWebServer @@ -15,10 +17,11 @@ def with_instrumented_server(auth=False, **ikwargs): Admin UI project. The arguments passed to the decorator are passed directly to the ``instrument()`` method of the server. """ + def decorator(f): @wraps(f) def wrapped(self, *args, **kwargs): - sio = socketio.Server(async_mode='threading') + sio = socketio.Server(async_mode="threading") @sio.event def enter_room(sid, data): @@ -28,12 +31,12 @@ def with_instrumented_server(auth=False, **ikwargs): def emit(sid, event): sio.emit(event, skip_sid=sid) - @sio.event(namespace='/foo') + @sio.event(namespace="/foo") def connect(sid, environ, auth): pass - if 'server_stats_interval' not in ikwargs: - ikwargs['server_stats_interval'] = 0.25 + if "server_stats_interval" not in ikwargs: + ikwargs["server_stats_interval"] = 0.25 self.isvr = sio.instrument(auth=auth, **ikwargs) server = SocketIOWebServer(sio) @@ -61,21 +64,23 @@ def with_instrumented_server(auth=False, **ikwargs): # logging.getLogger('socketio.client').setLevel(logging.NOTSET) return ret + return wrapped + return decorator def _custom_auth(auth): - return auth == {'foo': 'bar'} + return auth == {"foo": "bar"} class TestAdmin: def setup_method(self): - print('threads at start:', threading.enumerate()) + print("threads at start:", threading.enumerate()) self.thread_count = threading.active_count() def teardown_method(self): - print('threads at end:', threading.enumerate()) + print("threads at end:", threading.enumerate()) assert self.thread_count == threading.active_count() def _expect(self, expected, admin_client): @@ -91,192 +96,206 @@ class TestAdmin: return events def test_missing_auth(self): - sio = socketio.Server(async_mode='threading') + sio = socketio.Server(async_mode="threading") with pytest.raises(ValueError): sio.instrument() @with_instrumented_server(auth=False) def test_admin_connect_with_no_auth(self): with socketio.SimpleClient() as admin_client: - admin_client.connect('http://localhost:8900', namespace='/admin') + admin_client.connect("http://localhost:8900", namespace="/admin") with socketio.SimpleClient() as admin_client: - admin_client.connect('http://localhost:8900', namespace='/admin', - auth={'foo': 'bar'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "bar"} + ) - @with_instrumented_server(auth={'foo': 'bar'}) + @with_instrumented_server(auth={"foo": "bar"}) 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'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "bar"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): admin_client.connect( - 'http://localhost:8900', namespace='/admin', - auth={'foo': 'baz'}) + "http://localhost:8900", namespace="/admin", auth={"foo": "baz"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect( - 'http://localhost:8900', namespace='/admin') + admin_client.connect("http://localhost:8900", namespace="/admin") - @with_instrumented_server(auth=[{'foo': 'bar'}, - {'u': 'admin', 'p': 'secret'}]) + @with_instrumented_server(auth=[{"foo": "bar"}, {"u": "admin", "p": "secret"}]) 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'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "bar"} + ) with socketio.SimpleClient() as admin_client: - admin_client.connect('http://localhost:8900', namespace='/admin', - auth={'u': 'admin', 'p': 'secret'}) + admin_client.connect( + "http://localhost:8900", + namespace="/admin", + auth={"u": "admin", "p": "secret"}, + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect('http://localhost:8900', - namespace='/admin', auth={'foo': 'baz'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "baz"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect('http://localhost:8900', - namespace='/admin') + 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: - admin_client.connect('http://localhost:8900', namespace='/admin', - auth={'foo': 'bar'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "bar"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect('http://localhost:8900', - namespace='/admin', auth={'foo': 'baz'}) + admin_client.connect( + "http://localhost:8900", namespace="/admin", auth={"foo": "baz"} + ) with socketio.SimpleClient() as admin_client: with pytest.raises(ConnectionError): - admin_client.connect('http://localhost:8900', - namespace='/admin') + admin_client.connect("http://localhost:8900", namespace="/admin") @with_instrumented_server() def test_admin_connect_only_admin(self): with socketio.SimpleClient() as admin_client: - admin_client.connect('http://localhost:8900', namespace='/admin') + admin_client.connect("http://localhost:8900", namespace="/admin") sid = admin_client.sid - events = self._expect({'config': 1, 'all_sockets': 1, - 'server_stats': 2}, admin_client) - - assert 'supportedFeatures' in events['config'] - assert 'ALL_EVENTS' in events['config']['supportedFeatures'] - assert 'AGGREGATED_EVENTS' in events['config']['supportedFeatures'] - assert 'EMIT' in events['config']['supportedFeatures'] - assert len(events['all_sockets']) == 1 - assert events['all_sockets'][0]['id'] == sid - assert events['all_sockets'][0]['rooms'] == [sid] - assert events['server_stats']['clientsCount'] == 1 - assert events['server_stats']['pollingClientsCount'] == 0 - assert len(events['server_stats']['namespaces']) == 3 - assert {'name': '/', 'socketsCount': 0} in \ - events['server_stats']['namespaces'] - assert {'name': '/foo', 'socketsCount': 0} in \ - events['server_stats']['namespaces'] - assert {'name': '/admin', 'socketsCount': 1} in \ - events['server_stats']['namespaces'] + events = self._expect( + {"config": 1, "all_sockets": 1, "server_stats": 2}, admin_client + ) + + assert "supportedFeatures" in events["config"] + assert "ALL_EVENTS" in events["config"]["supportedFeatures"] + assert "AGGREGATED_EVENTS" in events["config"]["supportedFeatures"] + assert "EMIT" in events["config"]["supportedFeatures"] + assert len(events["all_sockets"]) == 1 + assert events["all_sockets"][0]["id"] == sid + assert events["all_sockets"][0]["rooms"] == [sid] + assert events["server_stats"]["clientsCount"] == 1 + assert events["server_stats"]["pollingClientsCount"] == 0 + assert len(events["server_stats"]["namespaces"]) == 3 + assert {"name": "/", "socketsCount": 0} in events["server_stats"]["namespaces"] + assert {"name": "/foo", "socketsCount": 0} in events["server_stats"][ + "namespaces" + ] + assert {"name": "/admin", "socketsCount": 1} in events["server_stats"][ + "namespaces" + ] @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: - client1.connect('http://localhost:8900') - client1.emit('enter_room', 'room') + with ( + socketio.SimpleClient() as client1, + socketio.SimpleClient() as client2, + socketio.SimpleClient() as client3, + socketio.SimpleClient() as admin_client, + ): + client1.connect("http://localhost:8900") + client1.emit("enter_room", "room") sid1 = client1.sid 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']) + client2.connect( + "http://localhost:8900", namespace="/foo", transports=["polling"] + ) sid2 = client2.sid self.isvr._check_for_upgrade = saved_check_for_upgrade - client3.connect('http://localhost:8900', namespace='/admin') + client3.connect("http://localhost:8900", namespace="/admin") sid3 = client3.sid - admin_client.connect('http://localhost:8900', namespace='/admin') + admin_client.connect("http://localhost:8900", namespace="/admin") sid = admin_client.sid - events = self._expect({'config': 1, 'all_sockets': 1, - 'server_stats': 2}, admin_client) - - assert 'supportedFeatures' in events['config'] - assert 'ALL_EVENTS' in events['config']['supportedFeatures'] - assert 'AGGREGATED_EVENTS' in events['config']['supportedFeatures'] - assert 'EMIT' in events['config']['supportedFeatures'] - assert len(events['all_sockets']) == 4 - assert events['server_stats']['clientsCount'] == 4 - assert events['server_stats']['pollingClientsCount'] == 1 - assert len(events['server_stats']['namespaces']) == 3 - assert {'name': '/', 'socketsCount': 1} in \ - events['server_stats']['namespaces'] - assert {'name': '/foo', 'socketsCount': 1} in \ - events['server_stats']['namespaces'] - assert {'name': '/admin', 'socketsCount': 2} in \ - events['server_stats']['namespaces'] - - for socket in events['all_sockets']: - if socket['id'] == sid: - assert socket['rooms'] == [sid] - elif socket['id'] == sid1: - assert socket['rooms'] == [sid1, 'room'] - elif socket['id'] == sid2: - assert socket['rooms'] == [sid2] - elif socket['id'] == sid3: - assert socket['rooms'] == [sid3] - - @with_instrumented_server(mode='production', read_only=True) + events = self._expect( + {"config": 1, "all_sockets": 1, "server_stats": 2}, admin_client + ) + + assert "supportedFeatures" in events["config"] + assert "ALL_EVENTS" in events["config"]["supportedFeatures"] + assert "AGGREGATED_EVENTS" in events["config"]["supportedFeatures"] + assert "EMIT" in events["config"]["supportedFeatures"] + assert len(events["all_sockets"]) == 4 + assert events["server_stats"]["clientsCount"] == 4 + assert events["server_stats"]["pollingClientsCount"] == 1 + assert len(events["server_stats"]["namespaces"]) == 3 + assert {"name": "/", "socketsCount": 1} in events["server_stats"]["namespaces"] + assert {"name": "/foo", "socketsCount": 1} in events["server_stats"][ + "namespaces" + ] + assert {"name": "/admin", "socketsCount": 2} in events["server_stats"][ + "namespaces" + ] + + for socket in events["all_sockets"]: + if socket["id"] == sid: + assert socket["rooms"] == [sid] + elif socket["id"] == sid1: + assert socket["rooms"] == [sid1, "room"] + elif socket["id"] == sid2: + assert socket["rooms"] == [sid2] + elif socket["id"] == sid3: + assert socket["rooms"] == [sid3] + + @with_instrumented_server(mode="production", read_only=True) 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}, - admin_client) - - assert 'supportedFeatures' in events['config'] - assert 'ALL_EVENTS' not in events['config']['supportedFeatures'] - assert 'AGGREGATED_EVENTS' in events['config']['supportedFeatures'] - assert 'EMIT' not in events['config']['supportedFeatures'] - assert events['server_stats']['clientsCount'] == 1 - assert events['server_stats']['pollingClientsCount'] == 0 - assert len(events['server_stats']['namespaces']) == 3 - assert {'name': '/', 'socketsCount': 0} in \ - events['server_stats']['namespaces'] - assert {'name': '/foo', 'socketsCount': 0} in \ - events['server_stats']['namespaces'] - assert {'name': '/admin', 'socketsCount': 1} in \ - events['server_stats']['namespaces'] + admin_client.connect("http://localhost:8900", namespace="/admin") + events = self._expect({"config": 1, "server_stats": 2}, admin_client) + + assert "supportedFeatures" in events["config"] + assert "ALL_EVENTS" not in events["config"]["supportedFeatures"] + assert "AGGREGATED_EVENTS" in events["config"]["supportedFeatures"] + assert "EMIT" not in events["config"]["supportedFeatures"] + assert events["server_stats"]["clientsCount"] == 1 + assert events["server_stats"]["pollingClientsCount"] == 0 + assert len(events["server_stats"]["namespaces"]) == 3 + assert {"name": "/", "socketsCount": 0} in events["server_stats"]["namespaces"] + assert {"name": "/foo", "socketsCount": 0} in events["server_stats"][ + "namespaces" + ] + assert {"name": "/admin", "socketsCount": 1} in events["server_stats"][ + "namespaces" + ] @with_instrumented_server() def test_admin_features(self): - with socketio.SimpleClient() as client1, \ - socketio.SimpleClient() as client2, \ - socketio.SimpleClient() as admin_client: - client1.connect('http://localhost:8900') - client2.connect('http://localhost:8900') - admin_client.connect('http://localhost:8900', namespace='/admin') + with ( + socketio.SimpleClient() as client1, + socketio.SimpleClient() as client2, + socketio.SimpleClient() as admin_client, + ): + client1.connect("http://localhost:8900") + client2.connect("http://localhost:8900") + admin_client.connect("http://localhost:8900", namespace="/admin") # emit from admin admin_client.emit( - 'emit', ('/', client1.sid, 'foo', {'bar': 'baz'}, 'extra')) + "emit", ("/", client1.sid, "foo", {"bar": "baz"}, "extra") + ) data = client1.receive(timeout=5) - assert data == ['foo', {'bar': 'baz'}, 'extra'] + assert data == ["foo", {"bar": "baz"}, "extra"] # emit from regular client - client1.emit('emit', 'foo') + client1.emit("emit", "foo") data = client2.receive(timeout=5) - assert data == ['foo'] + assert data == ["foo"] # join and leave - admin_client.emit('join', ('/', 'room', client1.sid)) + admin_client.emit("join", ("/", "room", client1.sid)) time.sleep(0.2) - admin_client.emit( - 'emit', ('/', 'room', 'foo', {'bar': 'baz'})) + admin_client.emit("emit", ("/", "room", "foo", {"bar": "baz"})) data = client1.receive(timeout=5) - assert data == ['foo', {'bar': 'baz'}] - admin_client.emit('leave', ('/', 'room')) + assert data == ["foo", {"bar": "baz"}] + admin_client.emit("leave", ("/", "room")) # disconnect - admin_client.emit('_disconnect', ('/', False, client1.sid)) + admin_client.emit("_disconnect", ("/", False, client1.sid)) for _ in range(10): if not client1.connected: break diff --git a/tests/common/test_client.py b/tests/common/test_client.py index 7ee2bac..d5cc2af 100644 --- a/tests/common/test_client.py +++ b/tests/common/test_client.py @@ -2,17 +2,17 @@ import logging import time from unittest import mock -from engineio import exceptions as engineio_exceptions -from engineio import json -from engineio import packet as engineio_packet import pytest +from engineio import exceptions as engineio_exceptions, json, packet as engineio_packet -from socketio import async_namespace -from socketio import client -from socketio import exceptions -from socketio import msgpack_packet -from socketio import namespace -from socketio import packet +from socketio import ( + async_namespace, + client, + exceptions, + msgpack_packet, + namespace, + packet, +) class TestClient: @@ -20,7 +20,7 @@ class TestClient: c = client.Client() assert not c.is_asyncio_based() - @mock.patch('socketio.client.Client._engineio_client_class') + @mock.patch("socketio.client.Client._engineio_client_class") def test_create(self, engineio_client_class): c = client.Client( reconnection=False, @@ -29,7 +29,7 @@ class TestClient: reconnection_delay_max=10, randomization_factor=0.2, handle_sigint=False, - foo='bar', + foo="bar", ) assert not c.reconnection assert c.reconnection_attempts == 123 @@ -37,8 +37,7 @@ class TestClient: assert c.reconnection_delay_max == 10 assert c.randomization_factor == 0.2 assert not c.handle_sigint - engineio_client_class().assert_called_once_with( - foo='bar', handle_sigint=False) + engineio_client_class().assert_called_once_with(foo="bar", handle_sigint=False) assert c.connection_url is None assert c.connection_headers is None assert c.connection_transports is None @@ -55,7 +54,7 @@ class TestClient: assert c.packet_class == packet.Packet def test_msgpack(self): - c = client.Client(serializer='msgpack') + c = client.Client(serializer="msgpack") assert c.packet_class == msgpack_packet.MsgPackPacket def test_custom_serializer(self): @@ -69,9 +68,9 @@ class TestClient: client.Client() assert packet.Packet.json == json assert engineio_packet.Packet.json == json - client.Client(json='foo') - assert packet.Packet.json == 'foo' - assert engineio_packet.Packet.json == 'foo' + client.Client(json="foo") + assert packet.Packet.json == "foo" + assert engineio_packet.Packet.json == "foo" packet.Packet.json = json def test_logger(self): @@ -84,32 +83,33 @@ class TestClient: c = client.Client(logger=True) assert c.logger.getEffectiveLevel() == logging.WARNING c.logger.setLevel(logging.NOTSET) - my_logger = logging.Logger('foo') + my_logger = logging.Logger("foo") c = client.Client(logger=my_logger) assert c.logger == my_logger - @mock.patch('socketio.client.Client._engineio_client_class') + @mock.patch("socketio.client.Client._engineio_client_class") def test_engineio_logger(self, engineio_client_class): - client.Client(engineio_logger='foo') + client.Client(engineio_logger="foo") engineio_client_class().assert_called_once_with( - handle_sigint=True, logger='foo') + handle_sigint=True, logger="foo" + ) def test_on_event(self): c = client.Client() - @c.on('connect') + @c.on("connect") def foo(): pass def bar(): pass - c.on('disconnect', bar) - c.on('disconnect', bar, namespace='/foo') + c.on("disconnect", bar) + c.on("disconnect", bar, namespace="/foo") - assert c.handlers['/']['connect'] == foo - assert c.handlers['/']['disconnect'] == bar - assert c.handlers['/foo']['disconnect'] == bar + assert c.handlers["/"]["connect"] == foo + assert c.handlers["/"]["disconnect"] == bar + assert c.handlers["/foo"]["disconnect"] == bar def test_event(self): c = client.Client() @@ -126,23 +126,23 @@ class TestClient: def bar(): pass - @c.event(namespace='/foo') + @c.event(namespace="/foo") def disconnect(): pass - assert c.handlers['/']['connect'] == connect - assert c.handlers['/']['foo'] == foo - assert c.handlers['/']['bar'] == bar - assert c.handlers['/foo']['disconnect'] == disconnect + assert c.handlers["/"]["connect"] == connect + assert c.handlers["/"]["foo"] == foo + assert c.handlers["/"]["bar"] == bar + assert c.handlers["/foo"]["disconnect"] == disconnect def test_namespace_handler(self): class MyNamespace(namespace.ClientNamespace): pass c = client.Client() - n = MyNamespace('/foo') + n = MyNamespace("/foo") c.register_namespace(n) - assert c.namespace_handlers['/foo'] == n + assert c.namespace_handlers["/foo"] == n def test_namespace_handler_wrong_class(self): class MyNamespace: @@ -150,7 +150,7 @@ class TestClient: pass c = client.Client() - n = MyNamespace('/foo') + n = MyNamespace("/foo") with pytest.raises(ValueError): c.register_namespace(n) @@ -159,7 +159,7 @@ class TestClient: pass c = client.Client() - n = MyNamespace('/foo') + n = MyNamespace("/foo") with pytest.raises(ValueError): c.register_namespace(n) @@ -167,130 +167,132 @@ class TestClient: c = client.Client() c.eio.connect = mock.MagicMock() c.connect( - 'url', - headers='headers', - auth='auth', - transports='transports', - namespaces=['/foo', '/', '/bar'], - socketio_path='path', + "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' - assert c.connection_auth == 'auth' - assert c.connection_transports == 'transports' - assert c.connection_namespaces == ['/foo', '/', '/bar'] - assert c.socketio_path == 'path' + assert c.connection_url == "url" + assert c.connection_headers == "headers" + assert c.connection_auth == "auth" + assert c.connection_transports == "transports" + assert c.connection_namespaces == ["/foo", "/", "/bar"] + assert c.socketio_path == "path" c.eio.connect.assert_called_once_with( - 'url', - headers='headers', - transports='transports', - engineio_path='path', + "url", + headers="headers", + transports="transports", + engineio_path="path", ) def test_connect_functions(self): c = client.Client() c.eio.connect = mock.MagicMock() c.connect( - lambda: 'url', - headers=lambda: 'headers', - auth='auth', - transports='transports', - namespaces=['/foo', '/', '/bar'], - socketio_path='path', + lambda: "url", + headers=lambda: "headers", + auth="auth", + transports="transports", + namespaces=["/foo", "/", "/bar"], + socketio_path="path", wait=False, ) c.eio.connect.assert_called_once_with( - 'url', - headers='headers', - transports='transports', - engineio_path='path', + "url", + headers="headers", + transports="transports", + engineio_path="path", ) def test_connect_one_namespace(self): c = client.Client() c.eio.connect = mock.MagicMock() c.connect( - 'url', - headers='headers', - transports='transports', - namespaces='/foo', - socketio_path='path', + "url", + headers="headers", + transports="transports", + namespaces="/foo", + socketio_path="path", wait=False, ) - assert c.connection_url == 'url' - assert c.connection_headers == 'headers' - assert c.connection_transports == 'transports' - assert c.connection_namespaces == ['/foo'] - assert c.socketio_path == 'path' + assert c.connection_url == "url" + assert c.connection_headers == "headers" + assert c.connection_transports == "transports" + assert c.connection_namespaces == ["/foo"] + assert c.socketio_path == "path" c.eio.connect.assert_called_once_with( - 'url', - headers='headers', - transports='transports', - engineio_path='path', + "url", + headers="headers", + transports="transports", + engineio_path="path", ) def test_connect_default_namespaces(self): c = client.Client() c.eio.connect = mock.MagicMock() - c.on('foo', mock.MagicMock(), namespace='/foo') - c.on('bar', mock.MagicMock(), namespace='/') - c.on('baz', mock.MagicMock(), namespace='*') + c.on("foo", mock.MagicMock(), namespace="/foo") + c.on("bar", mock.MagicMock(), namespace="/") + c.on("baz", mock.MagicMock(), namespace="*") c.connect( - 'url', - headers='headers', - transports='transports', - socketio_path='path', + "url", + headers="headers", + transports="transports", + socketio_path="path", wait=False, ) - assert c.connection_url == 'url' - assert c.connection_headers == 'headers' - assert c.connection_transports == 'transports' - assert c.connection_namespaces == ['/foo', '/'] or \ - c.connection_namespaces == ['/', '/foo'] - assert c.socketio_path == 'path' + assert c.connection_url == "url" + assert c.connection_headers == "headers" + assert c.connection_transports == "transports" + assert c.connection_namespaces == ["/foo", "/"] or c.connection_namespaces == [ + "/", + "/foo", + ] + assert c.socketio_path == "path" c.eio.connect.assert_called_once_with( - 'url', - headers='headers', - transports='transports', - engineio_path='path', + "url", + headers="headers", + transports="transports", + engineio_path="path", ) def test_connect_no_namespaces(self): c = client.Client() c.eio.connect = mock.MagicMock() c.connect( - 'url', - headers='headers', - transports='transports', - socketio_path='path', + "url", + headers="headers", + transports="transports", + socketio_path="path", wait=False, ) - assert c.connection_url == 'url' - assert c.connection_headers == 'headers' - assert c.connection_transports == 'transports' - assert c.connection_namespaces == ['/'] - assert c.socketio_path == 'path' + assert c.connection_url == "url" + assert c.connection_headers == "headers" + assert c.connection_transports == "transports" + assert c.connection_namespaces == ["/"] + assert c.socketio_path == "path" c.eio.connect.assert_called_once_with( - 'url', - headers='headers', - transports='transports', - engineio_path='path', + "url", + headers="headers", + transports="transports", + engineio_path="path", ) def test_connect_error(self): c = client.Client() c.eio.connect = mock.MagicMock( - side_effect=engineio_exceptions.ConnectionError('foo') + side_effect=engineio_exceptions.ConnectionError("foo") ) - c.on('foo', mock.MagicMock(), namespace='/foo') - c.on('bar', mock.MagicMock(), namespace='/') + c.on("foo", mock.MagicMock(), namespace="/foo") + c.on("bar", mock.MagicMock(), namespace="/") with pytest.raises(exceptions.ConnectionError): c.connect( - 'url', - headers='headers', - transports='transports', - socketio_path='path', + "url", + headers="headers", + transports="transports", + socketio_path="path", wait=False, ) @@ -298,12 +300,12 @@ class TestClient: c = client.Client() c.eio.connect = mock.MagicMock() c.connect( - 'url', + "url", wait=False, ) with pytest.raises(exceptions.ConnectionError): c.connect( - 'url', + "url", wait=False, ) @@ -314,12 +316,12 @@ class TestClient: def mock_connect(timeout): assert timeout == 0.01 - c.namespaces = {'/': '123'} + c.namespaces = {"/": "123"} return True c._connect_event.wait = mock_connect c.connect( - 'url', + "url", wait=True, wait_timeout=0.01, ) @@ -333,22 +335,22 @@ class TestClient: def mock_connect(timeout): assert timeout == 0.01 if c.namespaces == {}: - c.namespaces = {'/bar': '123'} + c.namespaces = {"/bar": "123"} return True - elif c.namespaces == {'/bar': '123'}: - c.namespaces = {'/bar': '123', '/foo': '456'} + if c.namespaces == {"/bar": "123"}: + c.namespaces = {"/bar": "123", "/foo": "456"} return True return False c._connect_event.wait = mock_connect c.connect( - 'url', - namespaces=['/foo', '/bar'], + "url", + namespaces=["/foo", "/bar"], wait=True, wait_timeout=0.01, ) assert c.connected is True - assert c.namespaces == {'/bar': '123', '/foo': '456'} + assert c.namespaces == {"/bar": "123", "/foo": "456"} def test_connect_timeout(self): c = client.Client() @@ -356,7 +358,7 @@ class TestClient: c.disconnect = mock.MagicMock() with pytest.raises(exceptions.ConnectionError): c.connect( - 'url', + "url", wait=True, wait_timeout=0.01, ) @@ -376,7 +378,7 @@ class TestClient: c.eio.wait = mock.MagicMock() c.sleep = mock.MagicMock() c._reconnect_task = mock.MagicMock() - states = ['disconnected'] + states = ["disconnected"] def fake_join(): c.eio.state = states.pop(0) @@ -391,7 +393,7 @@ class TestClient: c.eio.wait = mock.MagicMock() c.sleep = mock.MagicMock() c._reconnect_task = mock.MagicMock() - states = ['connected', 'disconnected'] + states = ["connected", "disconnected"] def fake_join(): c.eio.state = states.pop(0) @@ -403,200 +405,195 @@ class TestClient: def test_get_sid(self): c = client.Client() - c.namespaces = {'/': '1', '/foo': '2'} - assert c.get_sid() == '1' - assert c.get_sid('/') == '1' - assert c.get_sid('/foo') == '2' - assert c.get_sid('/bar') is None + c.namespaces = {"/": "1", "/foo": "2"} + assert c.get_sid() == "1" + assert c.get_sid("/") == "1" + assert c.get_sid("/foo") == "2" + assert c.get_sid("/bar") is None def test_emit_no_arguments(self): c = client.Client() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.MagicMock() - c.emit('foo') + c.emit("foo") expected_packet = packet.Packet( - packet.EVENT, namespace='/', data=['foo'], id=None) + packet.EVENT, namespace="/", data=["foo"], id=None + ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_one_argument(self): c = client.Client() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.MagicMock() - c.emit('foo', 'bar') + c.emit("foo", "bar") expected_packet = packet.Packet( packet.EVENT, - namespace='/', - data=['foo', 'bar'], + namespace="/", + data=["foo", "bar"], id=None, ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_one_argument_list(self): c = client.Client() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.MagicMock() - c.emit('foo', ['bar', 'baz']) + c.emit("foo", ["bar", "baz"]) expected_packet = packet.Packet( packet.EVENT, - namespace='/', - data=['foo', ['bar', 'baz']], + namespace="/", + data=["foo", ["bar", "baz"]], id=None, ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_two_arguments(self): c = client.Client() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.MagicMock() - c.emit('foo', ('bar', 'baz')) + c.emit("foo", ("bar", "baz")) expected_packet = packet.Packet( packet.EVENT, - namespace='/', - data=['foo', 'bar', 'baz'], + namespace="/", + data=["foo", "bar", "baz"], id=None, ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_namespace(self): c = client.Client() - c.namespaces = ['/foo'] + c.namespaces = ["/foo"] c._send_packet = mock.MagicMock() - c.emit('foo', namespace='/foo') + c.emit("foo", namespace="/foo") expected_packet = packet.Packet( - packet.EVENT, namespace='/foo', data=['foo'], id=None) + packet.EVENT, namespace="/foo", data=["foo"], id=None + ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_unknown_namespace(self): c = client.Client() - c.namespaces = ['/foo'] + c.namespaces = ["/foo"] with pytest.raises(exceptions.BadNamespaceError): - c.emit('foo', namespace='/bar') + c.emit("foo", namespace="/bar") def test_emit_with_callback(self): c = client.Client() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.MagicMock() c._generate_ack_id = mock.MagicMock(return_value=123) - c.emit('foo', callback='cb') + c.emit("foo", callback="cb") expected_packet = packet.Packet( - packet.EVENT, namespace='/', data=['foo'], id=123) + packet.EVENT, namespace="/", data=["foo"], id=123 + ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) - c._generate_ack_id.assert_called_once_with('/', 'cb') + c._generate_ack_id.assert_called_once_with("/", "cb") def test_emit_namespace_with_callback(self): c = client.Client() - c.namespaces = {'/foo': '1'} + c.namespaces = {"/foo": "1"} c._send_packet = mock.MagicMock() c._generate_ack_id = mock.MagicMock(return_value=123) - c.emit('foo', namespace='/foo', callback='cb') + c.emit("foo", namespace="/foo", callback="cb") expected_packet = packet.Packet( - packet.EVENT, namespace='/foo', data=['foo'], id=123) + packet.EVENT, namespace="/foo", data=["foo"], id=123 + ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) - c._generate_ack_id.assert_called_once_with('/foo', 'cb') + c._generate_ack_id.assert_called_once_with("/foo", "cb") def test_emit_binary(self): c = client.Client() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.MagicMock() - c.emit('foo', b'bar') + c.emit("foo", b"bar") expected_packet = packet.Packet( packet.EVENT, - namespace='/', - data=['foo', b'bar'], + namespace="/", + data=["foo", b"bar"], id=None, ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_emit_not_binary(self): c = client.Client() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._send_packet = mock.MagicMock() - c.emit('foo', 'bar') + c.emit("foo", "bar") expected_packet = packet.Packet( packet.EVENT, - namespace='/', - data=['foo', 'bar'], + namespace="/", + data=["foo", "bar"], id=None, ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_send(self): c = client.Client() c.emit = mock.MagicMock() - c.send('data', 'namespace', 'callback') + c.send("data", "namespace", "callback") c.emit.assert_called_once_with( - 'message', data='data', namespace='namespace', callback='callback' + "message", data="data", namespace="namespace", callback="callback" ) def test_send_with_defaults(self): c = client.Client() c.emit = mock.MagicMock() - c.send('data') + c.send("data") c.emit.assert_called_once_with( - 'message', data='data', namespace=None, callback=None + "message", data="data", namespace=None, callback=None ) def test_call(self): c = client.Client() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} def fake_event_wait(timeout=None): assert timeout == 60 - c._generate_ack_id.call_args_list[0][0][1]('foo', 321) + c._generate_ack_id.call_args_list[0][0][1]("foo", 321) return True c._send_packet = mock.MagicMock() c._generate_ack_id = mock.MagicMock(return_value=123) c.eio = mock.MagicMock() c.eio.create_event.return_value.wait = fake_event_wait - assert c.call('foo') == ('foo', 321) + assert c.call("foo") == ("foo", 321) expected_packet = packet.Packet( - packet.EVENT, namespace='/', data=['foo'], id=123) + packet.EVENT, namespace="/", data=["foo"], id=123 + ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_call_with_timeout(self): c = client.Client() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} def fake_event_wait(timeout=None): assert timeout == 12 @@ -607,69 +604,64 @@ class TestClient: c.eio = mock.MagicMock() c.eio.create_event.return_value.wait = fake_event_wait with pytest.raises(exceptions.TimeoutError): - c.call('foo', timeout=12) + c.call("foo", timeout=12) expected_packet = packet.Packet( - packet.EVENT, namespace='/', data=['foo'], id=123) + packet.EVENT, namespace="/", data=["foo"], id=123 + ) assert c._send_packet.call_count == 1 assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_disconnect(self): c = client.Client() c.connected = True - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._trigger_event = mock.MagicMock() c._send_packet = mock.MagicMock() c.eio = mock.MagicMock() - c.eio.state = 'connected' + c.eio.state = "connected" c.disconnect() assert c.connected assert c._trigger_event.call_count == 0 assert c._send_packet.call_count == 1 - expected_packet = packet.Packet(packet.DISCONNECT, namespace='/') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/") assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) c.eio.disconnect.assert_called_once_with() def test_disconnect_namespaces(self): c = client.Client() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._trigger_event = mock.MagicMock() c._send_packet = mock.MagicMock() c.eio = mock.MagicMock() - c.eio.state = 'connected' + c.eio.state = "connected" c.disconnect() assert c._trigger_event.call_count == 0 assert c._send_packet.call_count == 2 - expected_packet = packet.Packet(packet.DISCONNECT, namespace='/foo') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/foo") assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) - expected_packet = packet.Packet(packet.DISCONNECT, namespace='/bar') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/bar") assert ( - c._send_packet.call_args_list[1][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[1][0][0].encode() == expected_packet.encode() ) def test_transport(self): c = client.Client() - c.eio.transport = mock.MagicMock(return_value='foo') - assert c.transport() == 'foo' + c.eio.transport = mock.MagicMock(return_value="foo") + assert c.transport() == "foo" c.eio.transport.assert_called_once_with() def test_start_background_task(self): c = client.Client() - c.eio.start_background_task = mock.MagicMock(return_value='foo') - assert c.start_background_task('foo', 'bar', baz='baz') == 'foo' - c.eio.start_background_task.assert_called_once_with( - 'foo', 'bar', baz='baz' - ) + c.eio.start_background_task = mock.MagicMock(return_value="foo") + assert c.start_background_task("foo", "bar", baz="baz") == "foo" + c.eio.start_background_task.assert_called_once_with("foo", "bar", baz="baz") def test_sleep(self): c = client.Client() @@ -680,262 +672,259 @@ class TestClient: def test_send_packet(self): c = client.Client() c.eio.send = mock.MagicMock() - c._send_packet(packet.Packet(packet.EVENT, 'foo')) + c._send_packet(packet.Packet(packet.EVENT, "foo")) c.eio.send.assert_called_once_with('2"foo"') def test_send_packet_binary(self): c = client.Client() c.eio.send = mock.MagicMock() - c._send_packet(packet.Packet(packet.EVENT, b'foo')) + c._send_packet(packet.Packet(packet.EVENT, b"foo")) assert c.eio.send.call_args_list == [ mock.call('51-{"_placeholder":true,"num":0}'), - mock.call(b'foo'), + mock.call(b"foo"), ] or c.eio.send.call_args_list == [ mock.call('51-{"num":0,"_placeholder":true}'), - mock.call(b'foo'), + mock.call(b"foo"), ] def test_send_packet_default_binary(self): c = client.Client() c.eio.send = mock.MagicMock() - c._send_packet(packet.Packet(packet.EVENT, 'foo')) + c._send_packet(packet.Packet(packet.EVENT, "foo")) c.eio.send.assert_called_once_with('2"foo"') def test_generate_ack_id(self): c = client.Client() - assert c._generate_ack_id('/', 'cb') == 1 - assert c._generate_ack_id('/', 'cb') == 2 - assert c._generate_ack_id('/', 'cb') == 3 - assert c._generate_ack_id('/foo', 'cb') == 1 - assert c._generate_ack_id('/bar', 'cb') == 1 - assert c._generate_ack_id('/', 'cb') == 4 - assert c._generate_ack_id('/bar', 'cb') == 2 + assert c._generate_ack_id("/", "cb") == 1 + assert c._generate_ack_id("/", "cb") == 2 + assert c._generate_ack_id("/", "cb") == 3 + assert c._generate_ack_id("/foo", "cb") == 1 + assert c._generate_ack_id("/bar", "cb") == 1 + assert c._generate_ack_id("/", "cb") == 4 + assert c._generate_ack_id("/bar", "cb") == 2 def test_handle_connect(self): c = client.Client() c._connect_event = mock.MagicMock() c._trigger_event = mock.MagicMock() c._send_packet = mock.MagicMock() - c._handle_connect('/', {'sid': '123'}) - assert c.namespaces == {'/': '123'} + c._handle_connect("/", {"sid": "123"}) + assert c.namespaces == {"/": "123"} c._connect_event.set.assert_called_once_with() - c._trigger_event.assert_called_once_with('connect', namespace='/') + c._trigger_event.assert_called_once_with("connect", namespace="/") c._send_packet.assert_not_called() def test_handle_connect_with_namespaces(self): c = client.Client() - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._connect_event = mock.MagicMock() c._trigger_event = mock.MagicMock() c._send_packet = mock.MagicMock() - c._handle_connect('/', {'sid': '3'}) + c._handle_connect("/", {"sid": "3"}) c._connect_event.set.assert_called_once_with() - c._trigger_event.assert_called_once_with('connect', namespace='/') - assert c.namespaces == {'/': '3', '/foo': '1', '/bar': '2'} + c._trigger_event.assert_called_once_with("connect", namespace="/") + assert c.namespaces == {"/": "3", "/foo": "1", "/bar": "2"} def test_handle_connect_namespace(self): c = client.Client() - c.namespaces = {'/foo': '1'} + c.namespaces = {"/foo": "1"} c._connect_event = mock.MagicMock() c._trigger_event = mock.MagicMock() c._send_packet = mock.MagicMock() - c._handle_connect('/foo', {'sid': '123'}) - c._handle_connect('/bar', {'sid': '2'}) + c._handle_connect("/foo", {"sid": "123"}) + c._handle_connect("/bar", {"sid": "2"}) assert c._trigger_event.call_count == 1 c._connect_event.set.assert_called_once_with() - c._trigger_event.assert_called_once_with('connect', namespace='/bar') - assert c.namespaces == {'/foo': '1', '/bar': '2'} + c._trigger_event.assert_called_once_with("connect", namespace="/bar") + assert c.namespaces == {"/foo": "1", "/bar": "2"} def test_handle_disconnect(self): c = client.Client() - c.namespace = {'/': '1'} + c.namespace = {"/": "1"} c.connected = True c._trigger_event = mock.MagicMock() - c._handle_disconnect('/') - c._trigger_event.assert_any_call('disconnect', '/', - c.reason.SERVER_DISCONNECT) - c._trigger_event.assert_any_call('__disconnect_final', '/') + c._handle_disconnect("/") + 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('/') + c._handle_disconnect("/") assert c._trigger_event.call_count == 2 def test_handle_disconnect_namespace(self): c = client.Client() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._trigger_event = mock.MagicMock() - c._handle_disconnect('/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'} + c._handle_disconnect("/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', '/bar', - c.reason.SERVER_DISCONNECT) - c._trigger_event.assert_any_call('__disconnect_final', '/bar') + c._handle_disconnect("/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 def test_handle_disconnect_unknown_namespace(self): c = client.Client() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._trigger_event = mock.MagicMock() - c._handle_disconnect('/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'} + c._handle_disconnect("/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 def test_handle_disconnect_default_namespace(self): c = client.Client() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._trigger_event = mock.MagicMock() - c._handle_disconnect('/') - 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'} + c._handle_disconnect("/") + 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 def test_handle_event(self): c = client.Client() c._trigger_event = mock.MagicMock() - c._handle_event('/', None, ['foo', ('bar', 'baz')]) - c._trigger_event.assert_called_once_with('foo', '/', ('bar', 'baz')) + c._handle_event("/", None, ["foo", ("bar", "baz")]) + c._trigger_event.assert_called_once_with("foo", "/", ("bar", "baz")) def test_handle_event_with_id_no_arguments(self): c = client.Client() c._trigger_event = mock.MagicMock(return_value=None) c._send_packet = mock.MagicMock() - c._handle_event('/', 123, ['foo', ('bar', 'baz')]) - c._trigger_event.assert_called_once_with('foo', '/', ('bar', 'baz')) + c._handle_event("/", 123, ["foo", ("bar", "baz")]) + c._trigger_event.assert_called_once_with("foo", "/", ("bar", "baz")) assert c._send_packet.call_count == 1 - expected_packet = packet.Packet( - packet.ACK, namespace='/', id=123, data=[]) + expected_packet = packet.Packet(packet.ACK, namespace="/", id=123, data=[]) assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_handle_event_with_id_one_argument(self): c = client.Client() - c._trigger_event = mock.MagicMock(return_value='ret') + c._trigger_event = mock.MagicMock(return_value="ret") c._send_packet = mock.MagicMock() - c._handle_event('/', 123, ['foo', ('bar', 'baz')]) - c._trigger_event.assert_called_once_with('foo', '/', ('bar', 'baz')) + c._handle_event("/", 123, ["foo", ("bar", "baz")]) + c._trigger_event.assert_called_once_with("foo", "/", ("bar", "baz")) assert c._send_packet.call_count == 1 - expected_packet = packet.Packet( - packet.ACK, namespace='/', id=123, data=['ret']) + expected_packet = packet.Packet(packet.ACK, namespace="/", id=123, data=["ret"]) assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_handle_event_with_id_one_list_argument(self): c = client.Client() - c._trigger_event = mock.MagicMock(return_value=['a', 'b']) + c._trigger_event = mock.MagicMock(return_value=["a", "b"]) c._send_packet = mock.MagicMock() - c._handle_event('/', 123, ['foo', ('bar', 'baz')]) - c._trigger_event.assert_called_once_with('foo', '/', ('bar', 'baz')) + c._handle_event("/", 123, ["foo", ("bar", "baz")]) + c._trigger_event.assert_called_once_with("foo", "/", ("bar", "baz")) assert c._send_packet.call_count == 1 expected_packet = packet.Packet( - packet.ACK, namespace='/', id=123, data=[['a', 'b']]) + packet.ACK, namespace="/", id=123, data=[["a", "b"]] + ) assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_handle_event_with_id_two_arguments(self): c = client.Client() - c._trigger_event = mock.MagicMock(return_value=('a', 'b')) + c._trigger_event = mock.MagicMock(return_value=("a", "b")) c._send_packet = mock.MagicMock() - c._handle_event('/', 123, ['foo', ('bar', 'baz')]) - c._trigger_event.assert_called_once_with('foo', '/', ('bar', 'baz')) + c._handle_event("/", 123, ["foo", ("bar", "baz")]) + c._trigger_event.assert_called_once_with("foo", "/", ("bar", "baz")) assert c._send_packet.call_count == 1 expected_packet = packet.Packet( - packet.ACK, namespace='/', id=123, data=['a', 'b']) + packet.ACK, namespace="/", id=123, data=["a", "b"] + ) assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) def test_handle_ack(self): c = client.Client() mock_cb = mock.MagicMock() - c.callbacks['/foo'] = {123: mock_cb} - c._handle_ack('/foo', 123, ['bar', 'baz']) - mock_cb.assert_called_once_with('bar', 'baz') - assert 123 not in c.callbacks['/foo'] + c.callbacks["/foo"] = {123: mock_cb} + 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_not_found(self): c = client.Client() mock_cb = mock.MagicMock() - c.callbacks['/foo'] = {123: mock_cb} - c._handle_ack('/foo', 124, ['bar', 'baz']) + c.callbacks["/foo"] = {123: mock_cb} + c._handle_ack("/foo", 124, ["bar", "baz"]) mock_cb.assert_not_called() - assert 123 in c.callbacks['/foo'] + assert 123 in c.callbacks["/foo"] def test_handle_error(self): c = client.Client() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._connect_event = mock.MagicMock() c._trigger_event = mock.MagicMock() - c._handle_error('/', 'error') + c._handle_error("/", "error") assert c.namespaces == {} assert not c.connected c._connect_event.set.assert_called_once_with() - c._trigger_event.assert_called_once_with('connect_error', '/', 'error') + c._trigger_event.assert_called_once_with("connect_error", "/", "error") def test_handle_error_with_no_arguments(self): c = client.Client() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._connect_event = mock.MagicMock() c._trigger_event = mock.MagicMock() - c._handle_error('/', None) + c._handle_error("/", None) assert c.namespaces == {} assert not c.connected c._connect_event.set.assert_called_once_with() - c._trigger_event.assert_called_once_with('connect_error', '/') + c._trigger_event.assert_called_once_with("connect_error", "/") def test_handle_error_namespace(self): c = client.Client() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._connect_event = mock.MagicMock() c._trigger_event = mock.MagicMock() - c._handle_error('/bar', ['error', 'message']) - assert c.namespaces == {'/foo': '1'} + 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.assert_called_once_with( - 'connect_error', '/bar', 'error', 'message' + "connect_error", "/bar", "error", "message" ) def test_handle_error_namespace_with_no_arguments(self): c = client.Client() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._connect_event = mock.MagicMock() c._trigger_event = mock.MagicMock() - c._handle_error('/bar', None) - assert c.namespaces == {'/foo': '1'} + 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_called_once_with('connect_error', '/bar') + c._trigger_event.assert_called_once_with("connect_error", "/bar") def test_handle_error_unknown_namespace(self): c = client.Client() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._connect_event = mock.MagicMock() - c._handle_error('/baz', 'error') - assert c.namespaces == {'/foo': '1', '/bar': '2'} + c._handle_error("/baz", "error") + assert c.namespaces == {"/foo": "1", "/bar": "2"} assert c.connected c._connect_event.set.assert_called_once_with() @@ -943,24 +932,24 @@ class TestClient: c = client.Client() handler = mock.MagicMock() catchall_handler = mock.MagicMock() - c.on('foo', handler) - c.on('*', catchall_handler) - c._trigger_event('foo', '/', 1, '2') - c._trigger_event('bar', '/', 1, '2', 3) - c._trigger_event('connect', '/') # should not trigger - handler.assert_called_once_with(1, '2') - catchall_handler.assert_called_once_with('bar', 1, '2', 3) + c.on("foo", handler) + c.on("*", catchall_handler) + c._trigger_event("foo", "/", 1, "2") + c._trigger_event("bar", "/", 1, "2", 3) + 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): c = client.Client() handler = mock.MagicMock() catchall_handler = mock.MagicMock() - c.on('foo', handler, namespace='/bar') - c.on('*', catchall_handler, namespace='/bar') - c._trigger_event('foo', '/bar', 1, '2') - c._trigger_event('bar', '/bar', 1, '2', 3) - handler.assert_called_once_with(1, '2') - catchall_handler.assert_called_once_with('bar', 1, '2', 3) + c.on("foo", handler, namespace="/bar") + c.on("*", catchall_handler, namespace="/bar") + c._trigger_event("foo", "/bar", 1, "2") + c._trigger_event("bar", "/bar", 1, "2", 3) + handler.assert_called_once_with(1, "2") + catchall_handler.assert_called_once_with("bar", 1, "2", 3) def test_trigger_event_with_catchall_namespace(self): c = client.Client() @@ -969,56 +958,54 @@ class TestClient: msg_star_handler = mock.MagicMock() star_foo_handler = mock.MagicMock() star_star_handler = mock.MagicMock() - c.on('connect', connect_star_handler, namespace='*') - c.on('msg', msg_foo_handler, namespace='/foo') - c.on('msg', msg_star_handler, namespace='*') - c.on('*', star_foo_handler, namespace='/foo') - c.on('*', star_star_handler, namespace='*') - c._trigger_event('connect', '/bar') - c._trigger_event('msg', '/foo', 'a', 'b') - c._trigger_event('msg', '/bar', 'a', 'b') - c._trigger_event('my message', '/foo', 'a', 'b', 'c') - c._trigger_event('my message', '/bar', 'a', 'b', 'c') - c._trigger_event('disconnect', '/bar') - connect_star_handler.assert_called_once_with('/bar') - msg_foo_handler.assert_called_once_with('a', 'b') - msg_star_handler.assert_called_once_with('/bar', 'a', 'b') - star_foo_handler.assert_called_once_with( - 'my message', 'a', 'b', 'c') - star_star_handler.assert_called_once_with( - 'my message', '/bar', 'a', 'b', 'c') + c.on("connect", connect_star_handler, namespace="*") + c.on("msg", msg_foo_handler, namespace="/foo") + c.on("msg", msg_star_handler, namespace="*") + c.on("*", star_foo_handler, namespace="/foo") + c.on("*", star_star_handler, namespace="*") + c._trigger_event("connect", "/bar") + c._trigger_event("msg", "/foo", "a", "b") + c._trigger_event("msg", "/bar", "a", "b") + c._trigger_event("my message", "/foo", "a", "b", "c") + c._trigger_event("my message", "/bar", "a", "b", "c") + c._trigger_event("disconnect", "/bar") + connect_star_handler.assert_called_once_with("/bar") + msg_foo_handler.assert_called_once_with("a", "b") + msg_star_handler.assert_called_once_with("/bar", "a", "b") + star_foo_handler.assert_called_once_with("my message", "a", "b", "c") + star_star_handler.assert_called_once_with("my message", "/bar", "a", "b", "c") def test_trigger_event_with_catchall_namespace_handler(self): result = {} class MyNamespace(namespace.ClientNamespace): def on_connect(self, ns): - result['result'] = (ns,) + result["result"] = (ns,) def on_disconnect(self, ns, reason): - result['result'] = ('disconnect', ns, reason) + result["result"] = ("disconnect", ns, reason) def on_foo(self, ns, data): - result['result'] = (ns, data) + result["result"] = (ns, data) def on_bar(self, ns): - result['result'] = 'bar' + ns + result["result"] = "bar" + ns def on_baz(self, ns, data1, data2): - result['result'] = (ns, data1, data2) + result["result"] = (ns, data1, data2) c = client.Client() - c.register_namespace(MyNamespace('*')) - c._trigger_event('connect', '/foo') - assert result['result'] == ('/foo',) - c._trigger_event('foo', '/foo', 'a') - assert result['result'] == ('/foo', 'a') - c._trigger_event('bar', '/foo') - assert result['result'] == 'bar/foo' - c._trigger_event('baz', '/foo', 'a', 'b') - assert result['result'] == ('/foo', 'a', 'b') - c._trigger_event('disconnect', '/foo', 'bar') - assert result['result'] == ('disconnect', '/foo', 'bar') + c.register_namespace(MyNamespace("*")) + c._trigger_event("connect", "/foo") + assert result["result"] == ("/foo",) + c._trigger_event("foo", "/foo", "a") + assert result["result"] == ("/foo", "a") + c._trigger_event("bar", "/foo") + assert result["result"] == "bar/foo" + c._trigger_event("baz", "/foo", "a", "b") + assert result["result"] == ("/foo", "a", "b") + c._trigger_event("disconnect", "/foo", "bar") + assert result["result"] == ("disconnect", "/foo", "bar") def test_trigger_event_class_namespace(self): c = client.Client() @@ -1029,9 +1016,9 @@ class TestClient: result.append(a) result.append(b) - c.register_namespace(MyNamespace('/')) - c._trigger_event('foo', '/', 1, '2') - assert result == [1, '2'] + c.register_namespace(MyNamespace("/")) + c._trigger_event("foo", "/", 1, "2") + assert result == [1, "2"] def test_trigger_event_unknown_namespace(self): c = client.Client() @@ -1042,14 +1029,14 @@ class TestClient: result.append(a) result.append(b) - c.register_namespace(MyNamespace('/')) - c._trigger_event('foo', '/bar', 1, '2') + c.register_namespace(MyNamespace("/")) + c._trigger_event("foo", "/bar", 1, "2") assert result == [] - @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) + @mock.patch("socketio.client.random.random", side_effect=[1, 0, 0.5]) def test_handle_reconnect(self, random): c = client.Client() - c._reconnect_task = 'foo' + c._reconnect_task = "foo" c._reconnect_abort = c.eio.create_event() c._reconnect_abort.wait = mock.MagicMock(return_value=False) c.connect = mock.MagicMock( @@ -1064,10 +1051,10 @@ class TestClient: ] assert c._reconnect_task is None - @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) + @mock.patch("socketio.client.random.random", side_effect=[1, 0, 0.5]) def test_handle_reconnect_max_delay(self, random): c = client.Client(reconnection_delay_max=3) - c._reconnect_task = 'foo' + c._reconnect_task = "foo" c._reconnect_abort = c.eio.create_event() c._reconnect_abort.wait = mock.MagicMock(return_value=False) c.connect = mock.MagicMock( @@ -1082,11 +1069,11 @@ class TestClient: ] assert c._reconnect_task is None - @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) + @mock.patch("socketio.client.random.random", side_effect=[1, 0, 0.5]) def test_handle_reconnect_max_attempts(self, random): c = client.Client(reconnection_attempts=2) - c.connection_namespaces = ['/'] - c._reconnect_task = 'foo' + c.connection_namespaces = ["/"] + c._reconnect_task = "foo" c._reconnect_abort = c.eio.create_event() c._reconnect_abort.wait = mock.MagicMock(return_value=False) c._trigger_event = mock.MagicMock() @@ -1099,15 +1086,14 @@ class TestClient: mock.call(1.5), mock.call(1.5), ] - assert c._reconnect_task == 'foo' - c._trigger_event.assert_called_once_with('__disconnect_final', - namespace='/') + assert c._reconnect_task == "foo" + c._trigger_event.assert_called_once_with("__disconnect_final", namespace="/") - @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) + @mock.patch("socketio.client.random.random", side_effect=[1, 0, 0.5]) def test_handle_reconnect_aborted(self, random): c = client.Client() - c.connection_namespaces = ['/'] - c._reconnect_task = 'foo' + c.connection_namespaces = ["/"] + c._reconnect_task = "foo" c._reconnect_abort = c.eio.create_event() c._reconnect_abort.wait = mock.MagicMock(side_effect=[False, True]) c._trigger_event = mock.MagicMock() @@ -1118,54 +1104,50 @@ class TestClient: mock.call(1.5), mock.call(1.5), ] - assert c._reconnect_task == 'foo' - c._trigger_event.assert_called_once_with('__disconnect_final', - namespace='/') + assert c._reconnect_task == "foo" + c._trigger_event.assert_called_once_with("__disconnect_final", namespace="/") def test_shutdown_disconnect(self): c = client.Client() c.connected = True - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c._trigger_event = mock.MagicMock() c._send_packet = mock.MagicMock() c.eio = mock.MagicMock() - c.eio.state = 'connected' + c.eio.state = "connected" c.shutdown() assert c._trigger_event.call_count == 0 assert c._send_packet.call_count == 1 - expected_packet = packet.Packet(packet.DISCONNECT, namespace='/') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/") assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) c.eio.disconnect.assert_called_once_with() def test_shutdown_disconnect_namespaces(self): c = client.Client() c.connected = True - c.namespaces = {'/foo': '1', '/bar': '2'} + c.namespaces = {"/foo": "1", "/bar": "2"} c._trigger_event = mock.MagicMock() c._send_packet = mock.MagicMock() c.eio = mock.MagicMock() - c.eio.state = 'connected' + c.eio.state = "connected" c.shutdown() assert c._trigger_event.call_count == 0 assert c._send_packet.call_count == 2 - expected_packet = packet.Packet(packet.DISCONNECT, namespace='/foo') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/foo") assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) - expected_packet = packet.Packet(packet.DISCONNECT, namespace='/bar') + expected_packet = packet.Packet(packet.DISCONNECT, namespace="/bar") assert ( - c._send_packet.call_args_list[1][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[1][0][0].encode() == expected_packet.encode() ) - @mock.patch('socketio.client.random.random', side_effect=[1, 0, 0.5]) + @mock.patch("socketio.client.random.random", side_effect=[1, 0, 0.5]) def test_shutdown_reconnect(self, random): c = client.Client() - c.connection_namespaces = ['/'] + c.connection_namespaces = ["/"] c._reconnect_task = mock.MagicMock() c._trigger_event = mock.MagicMock() c.connect = mock.MagicMock(side_effect=exceptions.ConnectionError) @@ -1173,54 +1155,45 @@ class TestClient: time.sleep(0.1) c.shutdown() task.join() - c._trigger_event.assert_called_once_with('__disconnect_final', - namespace='/') + c._trigger_event.assert_called_once_with("__disconnect_final", namespace="/") c._reconnect_task.join.assert_called_once_with() def test_handle_eio_connect(self): c = client.Client() - c.connection_namespaces = ['/', '/foo'] - c.connection_auth = 'auth' + c.connection_namespaces = ["/", "/foo"] + c.connection_auth = "auth" c._send_packet = mock.MagicMock() - c.eio.sid = 'foo' + c.eio.sid = "foo" assert c.sid is None c._handle_eio_connect() - assert c.sid == 'foo' + assert c.sid == "foo" assert c._send_packet.call_count == 2 - expected_packet = packet.Packet( - packet.CONNECT, data='auth', namespace='/') + expected_packet = packet.Packet(packet.CONNECT, data="auth", namespace="/") assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) - expected_packet = packet.Packet( - packet.CONNECT, data='auth', namespace='/foo') + expected_packet = packet.Packet(packet.CONNECT, data="auth", namespace="/foo") assert ( - c._send_packet.call_args_list[1][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[1][0][0].encode() == expected_packet.encode() ) def test_handle_eio_connect_function(self): c = client.Client() - c.connection_namespaces = ['/', '/foo'] - c.connection_auth = lambda: 'auth' + c.connection_namespaces = ["/", "/foo"] + c.connection_auth = lambda: "auth" c._send_packet = mock.MagicMock() - c.eio.sid = 'foo' + c.eio.sid = "foo" assert c.sid is None c._handle_eio_connect() - assert c.sid == 'foo' + assert c.sid == "foo" assert c._send_packet.call_count == 2 - expected_packet = packet.Packet( - packet.CONNECT, data='auth', namespace='/') + expected_packet = packet.Packet(packet.CONNECT, data="auth", namespace="/") assert ( - c._send_packet.call_args_list[0][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[0][0][0].encode() == expected_packet.encode() ) - expected_packet = packet.Packet( - packet.CONNECT, data='auth', namespace='/foo') + expected_packet = packet.Packet(packet.CONNECT, data="auth", namespace="/foo") assert ( - c._send_packet.call_args_list[1][0][0].encode() - == expected_packet.encode() + c._send_packet.call_args_list[1][0][0].encode() == expected_packet.encode() ) def test_handle_eio_message(self): @@ -1232,101 +1205,99 @@ class TestClient: c._handle_error = mock.MagicMock() c._handle_eio_message('0{"sid":"123"}') - c._handle_connect.assert_called_with(None, {'sid': '123'}) + c._handle_connect.assert_called_with(None, {"sid": "123"}) c._handle_eio_message('0/foo,{"sid":"123"}') - c._handle_connect.assert_called_with('/foo', {'sid': '123'}) - c._handle_eio_message('1') + c._handle_connect.assert_called_with("/foo", {"sid": "123"}) + c._handle_eio_message("1") c._handle_disconnect.assert_called_with(None) - c._handle_eio_message('1/foo') - c._handle_disconnect.assert_called_with('/foo') + c._handle_eio_message("1/foo") + c._handle_disconnect.assert_called_with("/foo") c._handle_eio_message('2["foo"]') - c._handle_event.assert_called_with(None, None, ['foo']) + c._handle_event.assert_called_with(None, None, ["foo"]) c._handle_eio_message('3/foo,["bar"]') - c._handle_ack.assert_called_with('/foo', None, ['bar']) - c._handle_eio_message('4') + c._handle_ack.assert_called_with("/foo", None, ["bar"]) + c._handle_eio_message("4") c._handle_error.assert_called_with(None, None) c._handle_eio_message('4"foo"') - c._handle_error.assert_called_with(None, 'foo') + c._handle_error.assert_called_with(None, "foo") c._handle_eio_message('4["foo"]') - c._handle_error.assert_called_with(None, ['foo']) - c._handle_eio_message('4/foo') - c._handle_error.assert_called_with('/foo', None) + c._handle_error.assert_called_with(None, ["foo"]) + c._handle_eio_message("4/foo") + c._handle_error.assert_called_with("/foo", None) c._handle_eio_message('4/foo,["foo","bar"]') - c._handle_error.assert_called_with('/foo', ['foo', 'bar']) + c._handle_error.assert_called_with("/foo", ["foo", "bar"]) c._handle_eio_message('51-{"_placeholder":true,"num":0}') assert c._binary_packet.packet_type == packet.BINARY_EVENT - c._handle_eio_message(b'foo') - c._handle_event.assert_called_with(None, None, b'foo') + c._handle_eio_message(b"foo") + c._handle_event.assert_called_with(None, None, b"foo") 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 - c._handle_eio_message(b'bar') - c._handle_eio_message(b'foo') - c._handle_ack.assert_called_with( - '/foo', None, {'1': b'foo', '2': b'bar'} - ) + c._handle_eio_message(b"bar") + c._handle_eio_message(b"foo") + c._handle_ack.assert_called_with("/foo", None, {"1": b"foo", "2": b"bar"}) with pytest.raises(ValueError): - c._handle_eio_message('9') + c._handle_eio_message("9") def test_eio_disconnect(self): c = client.Client() - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c.connected = True c._trigger_event = mock.MagicMock() c.start_background_task = mock.MagicMock() - c.sid = 'foo' - c.eio.state = 'connected' - c._handle_eio_disconnect('foo') - c._trigger_event.assert_called_once_with('disconnect', '/', 'foo') + c.sid = "foo" + c.eio.state = "connected" + c._handle_eio_disconnect("foo") + c._trigger_event.assert_called_once_with("disconnect", "/", "foo") assert c.sid is None assert not c.connected def test_eio_disconnect_namespaces(self): c = client.Client() c.connected = True - c.namespaces = {'/': '1', '/foo': '2', '/bar': '3'} + c.namespaces = {"/": "1", "/foo": "2", "/bar": "3"} c._trigger_event = mock.MagicMock() c.start_background_task = mock.MagicMock() - c.sid = 'foo' - c.eio.state = 'connected' + c.sid = "foo" + c.eio.state = "connected" 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) + 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 def test_eio_disconnect_reconnect(self): c = client.Client(reconnection=True) c.start_background_task = mock.MagicMock() - c.eio.state = 'connected' + c.eio.state = "connected" 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.eio.state = "disconnected" c._handle_eio_disconnect(c.reason.CLIENT_DISCONNECT) c.start_background_task.assert_not_called() def test_eio_disconnect_no_reconnect(self): c = client.Client(reconnection=False) - c.namespaces = {'/': '1'} + c.namespaces = {"/": "1"} c.connected = True c._trigger_event = mock.MagicMock() c.start_background_task = mock.MagicMock() - c.sid = 'foo' - c.eio.state = 'connected' + c.sid = "foo" + c.eio.state = "connected" 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', '/') + 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_manager.py b/tests/common/test_manager.py index 5634c8a..5a71ab5 100644 --- a/tests/common/test_manager.py +++ b/tests/common/test_manager.py @@ -2,8 +2,7 @@ from unittest import mock import pytest -from socketio import manager -from socketio import packet +from socketio import manager, packet class TestBaseManager: @@ -23,329 +22,324 @@ class TestBaseManager: self.bm.initialize() def test_connect(self): - sid = 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] - assert sid in self.bm.rooms['/foo'][sid] - assert dict(self.bm.rooms['/foo'][None]) == {sid: '123'} - assert dict(self.bm.rooms['/foo'][sid]) == {sid: '123'} - assert self.bm.sid_from_eio_sid('123', '/foo') == sid - assert self.bm.sid_from_eio_sid('1234', '/foo') is None - assert self.bm.sid_from_eio_sid('123', '/bar') is None - assert self.bm.eio_sid_from_sid(sid, '/foo') == '123' - assert self.bm.eio_sid_from_sid('x', '/foo') is None - assert self.bm.eio_sid_from_sid(sid, '/bar') is None + sid = 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] + assert sid in self.bm.rooms["/foo"][sid] + assert dict(self.bm.rooms["/foo"][None]) == {sid: "123"} + assert dict(self.bm.rooms["/foo"][sid]) == {sid: "123"} + assert self.bm.sid_from_eio_sid("123", "/foo") == sid + assert self.bm.sid_from_eio_sid("1234", "/foo") is None + assert self.bm.sid_from_eio_sid("123", "/bar") is None + assert self.bm.eio_sid_from_sid(sid, "/foo") == "123" + assert self.bm.eio_sid_from_sid("x", "/foo") is None + assert self.bm.eio_sid_from_sid(sid, "/bar") is None def test_pre_disconnect(self): - sid1 = self.bm.connect('123', '/foo') - sid2 = 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]} - assert not self.bm.is_connected(sid1, '/foo') - assert self.bm.pre_disconnect(sid2, '/foo') == '456' - assert self.bm.pending_disconnect == {'/foo': [sid1, sid2]} - assert not self.bm.is_connected(sid2, '/foo') - self.bm.disconnect(sid1, '/foo') - assert self.bm.pending_disconnect == {'/foo': [sid2]} - self.bm.disconnect(sid2, '/foo') + sid1 = self.bm.connect("123", "/foo") + sid2 = 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]} + assert not self.bm.is_connected(sid1, "/foo") + assert self.bm.pre_disconnect(sid2, "/foo") == "456" + assert self.bm.pending_disconnect == {"/foo": [sid1, sid2]} + assert not self.bm.is_connected(sid2, "/foo") + self.bm.disconnect(sid1, "/foo") + assert self.bm.pending_disconnect == {"/foo": [sid2]} + self.bm.disconnect(sid2, "/foo") assert self.bm.pending_disconnect == {} def test_disconnect(self): - sid1 = self.bm.connect('123', '/foo') - sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') - self.bm.enter_room(sid2, '/foo', 'baz') - 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'} + sid1 = self.bm.connect("123", "/foo") + sid2 = self.bm.connect("456", "/foo") + self.bm.enter_room(sid1, "/foo", "bar") + self.bm.enter_room(sid2, "/foo", "baz") + 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 = self.bm.connect('123', '/') - sid2 = self.bm.connect('123', '/foo') - sid3 = self.bm.connect('456', '/') - sid4 = 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') - self.bm.disconnect(sid1, '/') - assert not self.bm.is_connected(sid1, '/') - assert self.bm.is_connected(sid2, '/foo') - 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'} + sid1 = self.bm.connect("123", "/") + sid2 = self.bm.connect("123", "/foo") + sid3 = self.bm.connect("456", "/") + sid4 = 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") + self.bm.disconnect(sid1, "/") + assert not self.bm.is_connected(sid1, "/") + assert self.bm.is_connected(sid2, "/foo") + 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 = self.bm.connect('123', '/') - sid2 = self.bm.connect('123', '/foo') - sid3 = self.bm.connect('456', '/') - sid4 = self.bm.connect('456', '/foo') - self.bm.disconnect(sid1, '/') - self.bm.disconnect(sid2, '/foo') - self.bm.disconnect(sid1, '/') - 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'} + sid1 = self.bm.connect("123", "/") + sid2 = self.bm.connect("123", "/foo") + sid3 = self.bm.connect("456", "/") + sid4 = self.bm.connect("456", "/foo") + self.bm.disconnect(sid1, "/") + self.bm.disconnect(sid2, "/foo") + self.bm.disconnect(sid1, "/") + 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 = self.bm.connect('123', '/foo') - sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') - self.bm.enter_room(sid2, '/foo', 'baz') - self.bm.disconnect(sid1, '/foo') - self.bm.disconnect(sid2, '/foo') + sid1 = self.bm.connect("123", "/foo") + sid2 = self.bm.connect("456", "/foo") + self.bm.enter_room(sid1, "/foo", "bar") + self.bm.enter_room(sid2, "/foo", "baz") + self.bm.disconnect(sid1, "/foo") + self.bm.disconnect(sid2, "/foo") assert self.bm.rooms == {} def test_disconnect_with_callbacks(self): - sid1 = self.bm.connect('123', '/') - sid2 = self.bm.connect('123', '/foo') - sid3 = 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') - self.bm.disconnect(sid2, '/foo') + sid1 = self.bm.connect("123", "/") + sid2 = self.bm.connect("123", "/foo") + sid3 = 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") + self.bm.disconnect(sid2, "/foo") assert sid2 not in self.bm.callbacks - self.bm.disconnect(sid1, '/') + self.bm.disconnect(sid1, "/") assert sid1 not in self.bm.callbacks assert sid3 in self.bm.callbacks def test_disconnect_bad_namespace(self): - self.bm.connect('123', '/') - self.bm.connect('123', '/foo') - self.bm.disconnect('123', '/bar') # should not assert + self.bm.connect("123", "/") + self.bm.connect("123", "/foo") + self.bm.disconnect("123", "/bar") # should not assert def test_enter_room_bad_namespace(self): - sid = self.bm.connect('123', '/') + sid = self.bm.connect("123", "/") with pytest.raises(ValueError): - self.bm.enter_room(sid, '/foo', 'bar') + self.bm.enter_room(sid, "/foo", "bar") def test_trigger_callback(self): - sid1 = self.bm.connect('123', '/') - sid2 = self.bm.connect('123', '/foo') + sid1 = self.bm.connect("123", "/") + sid2 = self.bm.connect("123", "/foo") cb = mock.MagicMock() id1 = self.bm._generate_ack_id(sid1, cb) id2 = self.bm._generate_ack_id(sid2, cb) id3 = self.bm._generate_ack_id(sid1, cb) - self.bm.trigger_callback(sid1, id1, ['foo']) - self.bm.trigger_callback(sid1, id3, ['bar']) - self.bm.trigger_callback(sid2, id2, ['bar', 'baz']) + self.bm.trigger_callback(sid1, id1, ["foo"]) + self.bm.trigger_callback(sid1, id3, ["bar"]) + self.bm.trigger_callback(sid2, id2, ["bar", "baz"]) assert cb.call_count == 3 - cb.assert_any_call('foo') - cb.assert_any_call('bar') - cb.assert_any_call('bar', 'baz') + cb.assert_any_call("foo") + cb.assert_any_call("bar") + cb.assert_any_call("bar", "baz") def test_invalid_callback(self): - sid = self.bm.connect('123', '/') + sid = self.bm.connect("123", "/") cb = mock.MagicMock() id = self.bm._generate_ack_id(sid, cb) # these should not raise an exception - self.bm.trigger_callback('xxx', id, ['foo']) - self.bm.trigger_callback(sid, id + 1, ['foo']) + self.bm.trigger_callback("xxx", id, ["foo"]) + self.bm.trigger_callback(sid, id + 1, ["foo"]) assert cb.call_count == 0 def test_get_namespaces(self): assert list(self.bm.get_namespaces()) == [] - self.bm.connect('123', '/') - self.bm.connect('123', '/foo') + self.bm.connect("123", "/") + self.bm.connect("123", "/foo") namespaces = list(self.bm.get_namespaces()) assert len(namespaces) == 2 - assert '/' in namespaces - assert '/foo' in namespaces + assert "/" in namespaces + assert "/foo" in namespaces def test_get_participants(self): - sid1 = self.bm.connect('123', '/') - sid2 = self.bm.connect('456', '/') - sid3 = self.bm.connect('789', '/') - self.bm.disconnect(sid3, '/') - assert sid3 not in self.bm.rooms['/'][None] - participants = list(self.bm.get_participants('/', None)) + sid1 = self.bm.connect("123", "/") + sid2 = self.bm.connect("456", "/") + sid3 = self.bm.connect("789", "/") + self.bm.disconnect(sid3, "/") + assert sid3 not in self.bm.rooms["/"][None] + participants = list(self.bm.get_participants("/", None)) assert len(participants) == 2 - assert (sid1, '123') in participants - assert (sid2, '456') in participants - assert (sid3, '789') not in participants + assert (sid1, "123") in participants + assert (sid2, "456") in participants + assert (sid3, "789") not in participants def test_leave_invalid_room(self): - sid = self.bm.connect('123', '/foo') - self.bm.leave_room(sid, '/foo', 'baz') - self.bm.leave_room(sid, '/bar', 'baz') + sid = self.bm.connect("123", "/foo") + self.bm.leave_room(sid, "/foo", "baz") + self.bm.leave_room(sid, "/bar", "baz") def test_no_room(self): - rooms = self.bm.get_rooms('123', '/foo') - assert [] == rooms + rooms = self.bm.get_rooms("123", "/foo") + assert rooms == [] def test_close_room(self): - sid1 = self.bm.connect('123', '/foo') - self.bm.connect('456', '/foo') - self.bm.connect('789', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') - self.bm.enter_room(sid1, '/foo', 'bar') - self.bm.close_room('bar', '/foo') - assert 'bar' not in self.bm.rooms['/foo'] + sid1 = self.bm.connect("123", "/foo") + self.bm.connect("456", "/foo") + self.bm.connect("789", "/foo") + self.bm.enter_room(sid1, "/foo", "bar") + self.bm.enter_room(sid1, "/foo", "bar") + self.bm.close_room("bar", "/foo") + assert "bar" not in self.bm.rooms["/foo"] def test_close_invalid_room(self): - self.bm.close_room('bar', '/foo') + self.bm.close_room("bar", "/foo") def test_rooms(self): - sid = self.bm.connect('123', '/foo') - self.bm.enter_room(sid, '/foo', 'bar') - r = self.bm.get_rooms(sid, '/foo') + sid = self.bm.connect("123", "/foo") + 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 + assert "bar" in r def test_emit_to_sid(self): - sid = self.bm.connect('123', '/foo') - self.bm.connect('456', '/foo') - self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo', to=sid) + sid = self.bm.connect("123", "/foo") + self.bm.connect("456", "/foo") + self.bm.emit("my event", {"foo": "bar"}, namespace="/foo", to=sid) assert self.bm.server._send_eio_packet.call_count == 1 - assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == '123' + 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] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' def test_emit_to_room(self): - sid1 = self.bm.connect('123', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') - sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid2, '/foo', 'bar') - self.bm.connect('789', '/foo') - self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo', room='bar') + sid1 = self.bm.connect("123", "/foo") + self.bm.enter_room(sid1, "/foo", "bar") + sid2 = self.bm.connect("456", "/foo") + self.bm.enter_room(sid2, "/foo", "bar") + self.bm.connect("789", "/foo") + self.bm.emit("my event", {"foo": "bar"}, namespace="/foo", room="bar") assert self.bm.server._send_eio_packet.call_count == 2 - assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == '123' - assert self.bm.server._send_eio_packet.call_args_list[1][0][0] == '456' + assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == "123" + assert self.bm.server._send_eio_packet.call_args_list[1][0][0] == "456" pkt = self.bm.server._send_eio_packet.call_args_list[0][0][1] assert pkt == self.bm.server._send_eio_packet.call_args_list[1][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' def test_emit_to_rooms(self): - sid1 = self.bm.connect('123', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') - sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid2, '/foo', 'bar') - self.bm.enter_room(sid2, '/foo', 'baz') - sid3 = self.bm.connect('789', '/foo') - self.bm.enter_room(sid3, '/foo', 'baz') - self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo', - room=['bar', 'baz']) + sid1 = self.bm.connect("123", "/foo") + self.bm.enter_room(sid1, "/foo", "bar") + sid2 = self.bm.connect("456", "/foo") + self.bm.enter_room(sid2, "/foo", "bar") + self.bm.enter_room(sid2, "/foo", "baz") + sid3 = self.bm.connect("789", "/foo") + self.bm.enter_room(sid3, "/foo", "baz") + self.bm.emit("my event", {"foo": "bar"}, namespace="/foo", room=["bar", "baz"]) assert self.bm.server._send_eio_packet.call_count == 3 - assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == '123' - assert self.bm.server._send_eio_packet.call_args_list[1][0][0] == '456' - assert self.bm.server._send_eio_packet.call_args_list[2][0][0] == '789' + assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == "123" + assert self.bm.server._send_eio_packet.call_args_list[1][0][0] == "456" + assert self.bm.server._send_eio_packet.call_args_list[2][0][0] == "789" pkt = self.bm.server._send_eio_packet.call_args_list[0][0][1] assert pkt == self.bm.server._send_eio_packet.call_args_list[1][0][1] assert pkt == self.bm.server._send_eio_packet.call_args_list[2][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' def test_emit_to_all(self): - sid1 = self.bm.connect('123', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') - sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid2, '/foo', 'bar') - self.bm.connect('789', '/foo') - self.bm.connect('abc', '/bar') - self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo') + sid1 = self.bm.connect("123", "/foo") + self.bm.enter_room(sid1, "/foo", "bar") + sid2 = self.bm.connect("456", "/foo") + self.bm.enter_room(sid2, "/foo", "bar") + self.bm.connect("789", "/foo") + self.bm.connect("abc", "/bar") + self.bm.emit("my event", {"foo": "bar"}, namespace="/foo") assert self.bm.server._send_eio_packet.call_count == 3 - assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == '123' - assert self.bm.server._send_eio_packet.call_args_list[1][0][0] == '456' - assert self.bm.server._send_eio_packet.call_args_list[2][0][0] == '789' + assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == "123" + assert self.bm.server._send_eio_packet.call_args_list[1][0][0] == "456" + assert self.bm.server._send_eio_packet.call_args_list[2][0][0] == "789" pkt = self.bm.server._send_eio_packet.call_args_list[0][0][1] assert pkt == self.bm.server._send_eio_packet.call_args_list[1][0][1] assert pkt == self.bm.server._send_eio_packet.call_args_list[2][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' def test_emit_to_all_skip_one(self): - sid1 = self.bm.connect('123', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') - sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid2, '/foo', 'bar') - self.bm.connect('789', '/foo') - self.bm.connect('abc', '/bar') - self.bm.emit( - 'my event', {'foo': 'bar'}, namespace='/foo', skip_sid=sid2 - ) + sid1 = self.bm.connect("123", "/foo") + self.bm.enter_room(sid1, "/foo", "bar") + sid2 = self.bm.connect("456", "/foo") + self.bm.enter_room(sid2, "/foo", "bar") + self.bm.connect("789", "/foo") + self.bm.connect("abc", "/bar") + self.bm.emit("my event", {"foo": "bar"}, namespace="/foo", skip_sid=sid2) assert self.bm.server._send_eio_packet.call_count == 2 - assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == '123' - assert self.bm.server._send_eio_packet.call_args_list[1][0][0] == '789' + assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == "123" + assert self.bm.server._send_eio_packet.call_args_list[1][0][0] == "789" pkt = self.bm.server._send_eio_packet.call_args_list[0][0][1] assert pkt == self.bm.server._send_eio_packet.call_args_list[1][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' def test_emit_to_all_skip_two(self): - sid1 = self.bm.connect('123', '/foo') - self.bm.enter_room(sid1, '/foo', 'bar') - sid2 = self.bm.connect('456', '/foo') - self.bm.enter_room(sid2, '/foo', 'bar') - sid3 = self.bm.connect('789', '/foo') - self.bm.connect('abc', '/bar') + sid1 = self.bm.connect("123", "/foo") + self.bm.enter_room(sid1, "/foo", "bar") + sid2 = self.bm.connect("456", "/foo") + self.bm.enter_room(sid2, "/foo", "bar") + sid3 = self.bm.connect("789", "/foo") + self.bm.connect("abc", "/bar") self.bm.emit( - 'my event', - {'foo': 'bar'}, - namespace='/foo', + "my event", + {"foo": "bar"}, + namespace="/foo", skip_sid=[sid1, sid3], ) assert self.bm.server._send_eio_packet.call_count == 1 - assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == '456' + assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == "456" pkt = self.bm.server._send_eio_packet.call_args_list[0][0][1] assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]' def test_emit_with_callback(self): - sid = self.bm.connect('123', '/foo') + sid = self.bm.connect("123", "/foo") self.bm._generate_ack_id = mock.MagicMock() self.bm._generate_ack_id.return_value = 11 - self.bm.emit( - 'my event', {'foo': 'bar'}, namespace='/foo', callback='cb' - ) - self.bm._generate_ack_id.assert_called_once_with(sid, 'cb') + 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.call_count == 1 - assert self.bm.server._send_packet.call_args_list[0][0][0] == '123' + assert self.bm.server._send_packet.call_args_list[0][0][0] == "123" pkt = self.bm.server._send_packet.call_args_list[0][0][1] assert pkt.encode() == '2/foo,11["my event",{"foo":"bar"}]' def test_emit_to_invalid_room(self): - self.bm.emit('my event', {'foo': 'bar'}, namespace='/', room='123') + self.bm.emit("my event", {"foo": "bar"}, namespace="/", room="123") def test_emit_to_invalid_namespace(self): - self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo') + self.bm.emit("my event", {"foo": "bar"}, namespace="/foo") def test_emit_with_tuple(self): - sid = self.bm.connect('123', '/foo') - self.bm.emit('my event', ('foo', 'bar'), namespace='/foo', room=sid) + sid = self.bm.connect("123", "/foo") + self.bm.emit("my event", ("foo", "bar"), namespace="/foo", room=sid) assert self.bm.server._send_eio_packet.call_count == 1 - assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == '123' + 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] assert pkt.encode() == '42/foo,["my event","foo","bar"]' def test_emit_with_list(self): - sid = self.bm.connect('123', '/foo') - self.bm.emit('my event', ['foo', 'bar'], namespace='/foo', room=sid) + sid = self.bm.connect("123", "/foo") + self.bm.emit("my event", ["foo", "bar"], namespace="/foo", room=sid) assert self.bm.server._send_eio_packet.call_count == 1 - assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == '123' + 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] assert pkt.encode() == '42/foo,["my event",["foo","bar"]]' def test_emit_with_none(self): - sid = self.bm.connect('123', '/foo') - self.bm.emit('my event', None, namespace='/foo', room=sid) + sid = self.bm.connect("123", "/foo") + self.bm.emit("my event", None, namespace="/foo", room=sid) assert self.bm.server._send_eio_packet.call_count == 1 - assert self.bm.server._send_eio_packet.call_args_list[0][0][0] == '123' + 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] assert pkt.encode() == '42/foo,["my event"]' def test_emit_binary(self): - sid = self.bm.connect('123', '/') - self.bm.emit('my event', b'my binary data', namespace='/', room=sid) + sid = self.bm.connect("123", "/") + 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' + 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] assert pkt.encode() == '451-["my event",{"_placeholder":true,"num":0}]' - assert self.bm.server._send_eio_packet.call_args_list[1][0][0] == '123' + assert self.bm.server._send_eio_packet.call_args_list[1][0][0] == "123" pkt = self.bm.server._send_eio_packet.call_args_list[1][0][1] - assert pkt.encode() == b'my binary data' + assert pkt.encode() == b"my binary data" diff --git a/tests/common/test_middleware.py b/tests/common/test_middleware.py index 0579503..32763b2 100644 --- a/tests/common/test_middleware.py +++ b/tests/common/test_middleware.py @@ -6,33 +6,31 @@ from socketio import middleware class TestMiddleware: def test_wsgi_routing(self): mock_wsgi_app = mock.MagicMock() - mock_sio_app = 'foo' + mock_sio_app = "foo" m = middleware.Middleware(mock_sio_app, mock_wsgi_app) - environ = {'PATH_INFO': '/foo'} + environ = {"PATH_INFO": "/foo"} start_response = "foo" m(environ, start_response) mock_wsgi_app.assert_called_once_with(environ, start_response) def test_sio_routing(self): - mock_wsgi_app = 'foo' + mock_wsgi_app = "foo" mock_sio_app = mock.Mock() mock_sio_app.handle_request = mock.MagicMock() m = middleware.Middleware(mock_sio_app, mock_wsgi_app) - environ = {'PATH_INFO': '/socket.io/'} + environ = {"PATH_INFO": "/socket.io/"} start_response = "foo" m(environ, start_response) - mock_sio_app.handle_request.assert_called_once_with( - environ, start_response - ) + mock_sio_app.handle_request.assert_called_once_with(environ, start_response) def test_404(self): mock_wsgi_app = None mock_sio_app = mock.Mock() m = middleware.Middleware(mock_sio_app, mock_wsgi_app) - environ = {'PATH_INFO': '/foo/bar'} + environ = {"PATH_INFO": "/foo/bar"} start_response = mock.MagicMock() r = m(environ, start_response) - assert r == [b'Not Found'] + assert r == [b"Not Found"] start_response.assert_called_once_with( - "404 Not Found", [('Content-Type', 'text/plain')] + "404 Not Found", [("Content-Type", "text/plain")] ) diff --git a/tests/common/test_msgpack_packet.py b/tests/common/test_msgpack_packet.py index e0197a2..18ffc69 100644 --- a/tests/common/test_msgpack_packet.py +++ b/tests/common/test_msgpack_packet.py @@ -1,11 +1,11 @@ -from socketio import msgpack_packet -from socketio import packet +from socketio import msgpack_packet, packet class TestMsgPackPacket: def test_encode_decode(self): p = msgpack_packet.MsgPackPacket( - packet.CONNECT, data={'auth': {'token': '123'}}, namespace='/foo') + packet.CONNECT, data={"auth": {"token": "123"}}, namespace="/foo" + ) p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode()) assert p.packet_type == p2.packet_type assert p.data == p2.data @@ -14,7 +14,8 @@ class TestMsgPackPacket: def test_encode_decode_with_id(self): p = msgpack_packet.MsgPackPacket( - packet.EVENT, data=['ev', 42], id=123, namespace='/foo') + packet.EVENT, data=["ev", 42], id=123, namespace="/foo" + ) p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode()) assert p.packet_type == p2.packet_type assert p.data == p2.data @@ -22,13 +23,13 @@ class TestMsgPackPacket: assert p.namespace == p2.namespace def test_encode_binary_event_packet(self): - p = msgpack_packet.MsgPackPacket(packet.EVENT, data={'foo': b'bar'}) + p = msgpack_packet.MsgPackPacket(packet.EVENT, data={"foo": b"bar"}) assert p.packet_type == packet.EVENT p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode()) - assert p2.data == {'foo': b'bar'} + assert p2.data == {"foo": b"bar"} def test_encode_binary_ack_packet(self): - p = msgpack_packet.MsgPackPacket(packet.ACK, data={'foo': b'bar'}) + p = msgpack_packet.MsgPackPacket(packet.ACK, data={"foo": b"bar"}) assert p.packet_type == packet.ACK p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode()) - assert p2.data == {'foo': b'bar'} + assert p2.data == {"foo": b"bar"} diff --git a/tests/common/test_namespace.py b/tests/common/test_namespace.py index f1476e4..3d8f897 100644 --- a/tests/common/test_namespace.py +++ b/tests/common/test_namespace.py @@ -9,298 +9,283 @@ class TestNamespace: class MyNamespace(namespace.Namespace): def on_connect(self, sid, environ): - result['result'] = (sid, environ) + result["result"] = (sid, environ) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - ns.trigger_event('connect', 'sid', {'foo': 'bar'}) - assert result['result'] == ('sid', {'foo': 'bar'}) + ns.trigger_event("connect", "sid", {"foo": "bar"}) + assert result["result"] == ("sid", {"foo": "bar"}) def test_disconnect_event(self): result = {} class MyNamespace(namespace.Namespace): def on_disconnect(self, sid, reason): - result['result'] = (sid, reason) + result["result"] = (sid, reason) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - ns.trigger_event('disconnect', 'sid', 'foo') - assert result['result'] == ('sid', 'foo') + 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 + result["result"] = sid - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - ns.trigger_event('disconnect', 'sid', 'foo') - assert result['result'] == 'sid' + ns.trigger_event("disconnect", "sid", "foo") + assert result["result"] == "sid" def test_event(self): result = {} class MyNamespace(namespace.Namespace): def on_custom_message(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - ns.trigger_event('custom_message', 'sid', {'data': 'data'}) - assert result['result'] == ('sid', {'data': 'data'}) + ns.trigger_event("custom_message", "sid", {"data": "data"}) + assert result["result"] == ("sid", {"data": "data"}) def test_event_not_found(self): result = {} class MyNamespace(namespace.Namespace): def on_custom_message(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_server(mock.MagicMock()) - ns.trigger_event('another_custom_message', 'sid', {'data': 'data'}) + ns.trigger_event("another_custom_message", "sid", {"data": "data"}) assert result == {} def test_emit(self): - ns = namespace.Namespace('/foo') + ns = namespace.Namespace("/foo") ns._set_server(mock.MagicMock()) - ns.emit('ev', data='data', to='room', skip_sid='skip', callback='cb') + ns.emit("ev", data="data", to="room", skip_sid="skip", callback="cb") ns.server.emit.assert_called_with( - 'ev', - data='data', - to='room', + "ev", + data="data", + to="room", room=None, - skip_sid='skip', - namespace='/foo', - callback='cb', + skip_sid="skip", + namespace="/foo", + callback="cb", ignore_queue=False, ) ns.emit( - 'ev', - data='data', - room='room', - skip_sid='skip', - namespace='/bar', - callback='cb', + "ev", + data="data", + room="room", + skip_sid="skip", + namespace="/bar", + callback="cb", ignore_queue=True, ) ns.server.emit.assert_called_with( - 'ev', - data='data', + "ev", + data="data", to=None, - room='room', - skip_sid='skip', - namespace='/bar', - callback='cb', + room="room", + skip_sid="skip", + namespace="/bar", + callback="cb", ignore_queue=True, ) def test_send(self): - ns = namespace.Namespace('/foo') + ns = namespace.Namespace("/foo") ns._set_server(mock.MagicMock()) - ns.send(data='data', to='room', skip_sid='skip', callback='cb') + ns.send(data="data", to="room", skip_sid="skip", callback="cb") ns.server.send.assert_called_with( - 'data', - to='room', + "data", + to="room", room=None, - skip_sid='skip', - namespace='/foo', - callback='cb', + skip_sid="skip", + namespace="/foo", + callback="cb", ignore_queue=False, ) ns.send( - data='data', - room='room', - skip_sid='skip', - namespace='/bar', - callback='cb', + data="data", + room="room", + skip_sid="skip", + namespace="/bar", + callback="cb", ignore_queue=True, ) ns.server.send.assert_called_with( - 'data', + "data", to=None, - room='room', - skip_sid='skip', - namespace='/bar', - callback='cb', + room="room", + skip_sid="skip", + namespace="/bar", + callback="cb", ignore_queue=True, ) def test_call(self): - ns = namespace.Namespace('/foo') + ns = namespace.Namespace("/foo") ns._set_server(mock.MagicMock()) - ns.call('ev', data='data', to='sid') + ns.call("ev", data="data", to="sid") ns.server.call.assert_called_with( - 'ev', - data='data', - to='sid', + "ev", + data="data", + to="sid", sid=None, - namespace='/foo', + namespace="/foo", timeout=None, ignore_queue=False, ) ns.call( - 'ev', - data='data', - sid='sid', - namespace='/bar', + "ev", + data="data", + sid="sid", + namespace="/bar", timeout=45, ignore_queue=True, ) ns.server.call.assert_called_with( - 'ev', - data='data', + "ev", + data="data", to=None, - sid='sid', - namespace='/bar', + sid="sid", + namespace="/bar", timeout=45, ignore_queue=True, ) def test_enter_room(self): - ns = namespace.Namespace('/foo') + ns = namespace.Namespace("/foo") ns._set_server(mock.MagicMock()) - ns.enter_room('sid', 'room') - ns.server.enter_room.assert_called_with( - 'sid', 'room', namespace='/foo' - ) - ns.enter_room('sid', 'room', namespace='/bar') - ns.server.enter_room.assert_called_with( - 'sid', 'room', namespace='/bar' - ) + ns.enter_room("sid", "room") + ns.server.enter_room.assert_called_with("sid", "room", namespace="/foo") + ns.enter_room("sid", "room", namespace="/bar") + ns.server.enter_room.assert_called_with("sid", "room", namespace="/bar") def test_leave_room(self): - ns = namespace.Namespace('/foo') + ns = namespace.Namespace("/foo") ns._set_server(mock.MagicMock()) - ns.leave_room('sid', 'room') - ns.server.leave_room.assert_called_with( - 'sid', 'room', namespace='/foo' - ) - ns.leave_room('sid', 'room', namespace='/bar') - ns.server.leave_room.assert_called_with( - 'sid', 'room', namespace='/bar' - ) + ns.leave_room("sid", "room") + ns.server.leave_room.assert_called_with("sid", "room", namespace="/foo") + ns.leave_room("sid", "room", namespace="/bar") + ns.server.leave_room.assert_called_with("sid", "room", namespace="/bar") def test_close_room(self): - ns = namespace.Namespace('/foo') + ns = namespace.Namespace("/foo") ns._set_server(mock.MagicMock()) - ns.close_room('room') - ns.server.close_room.assert_called_with('room', namespace='/foo') - ns.close_room('room', namespace='/bar') - ns.server.close_room.assert_called_with('room', namespace='/bar') + ns.close_room("room") + ns.server.close_room.assert_called_with("room", namespace="/foo") + ns.close_room("room", namespace="/bar") + ns.server.close_room.assert_called_with("room", namespace="/bar") def test_rooms(self): - ns = namespace.Namespace('/foo') + ns = namespace.Namespace("/foo") ns._set_server(mock.MagicMock()) - ns.rooms('sid') - ns.server.rooms.assert_called_with('sid', namespace='/foo') - ns.rooms('sid', namespace='/bar') - ns.server.rooms.assert_called_with('sid', namespace='/bar') + ns.rooms("sid") + ns.server.rooms.assert_called_with("sid", namespace="/foo") + ns.rooms("sid", namespace="/bar") + ns.server.rooms.assert_called_with("sid", namespace="/bar") def test_session(self): - ns = namespace.Namespace('/foo') + ns = namespace.Namespace("/foo") ns._set_server(mock.MagicMock()) - ns.get_session('sid') - ns.server.get_session.assert_called_with('sid', namespace='/foo') - ns.get_session('sid', namespace='/bar') - ns.server.get_session.assert_called_with('sid', namespace='/bar') - ns.save_session('sid', {'a': 'b'}) - ns.server.save_session.assert_called_with( - 'sid', {'a': 'b'}, namespace='/foo' - ) - ns.save_session('sid', {'a': 'b'}, namespace='/bar') - ns.server.save_session.assert_called_with( - 'sid', {'a': 'b'}, namespace='/bar' - ) - ns.session('sid') - ns.server.session.assert_called_with('sid', namespace='/foo') - ns.session('sid', namespace='/bar') - ns.server.session.assert_called_with('sid', namespace='/bar') + ns.get_session("sid") + ns.server.get_session.assert_called_with("sid", namespace="/foo") + ns.get_session("sid", namespace="/bar") + ns.server.get_session.assert_called_with("sid", namespace="/bar") + ns.save_session("sid", {"a": "b"}) + ns.server.save_session.assert_called_with("sid", {"a": "b"}, namespace="/foo") + ns.save_session("sid", {"a": "b"}, namespace="/bar") + ns.server.save_session.assert_called_with("sid", {"a": "b"}, namespace="/bar") + ns.session("sid") + ns.server.session.assert_called_with("sid", namespace="/foo") + ns.session("sid", namespace="/bar") + ns.server.session.assert_called_with("sid", namespace="/bar") def test_disconnect(self): - ns = namespace.Namespace('/foo') + ns = namespace.Namespace("/foo") ns._set_server(mock.MagicMock()) - ns.disconnect('sid') - ns.server.disconnect.assert_called_with('sid', namespace='/foo') - ns.disconnect('sid', namespace='/bar') - ns.server.disconnect.assert_called_with('sid', namespace='/bar') + ns.disconnect("sid") + ns.server.disconnect.assert_called_with("sid", namespace="/foo") + 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 + result["result"] = reason - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_client(mock.MagicMock()) - ns.trigger_event('disconnect', 'foo') - assert result['result'] == 'foo' + 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' + result["result"] = "ok" - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_client(mock.MagicMock()) - ns.trigger_event('disconnect', 'foo') - assert result['result'] == 'ok' + ns.trigger_event("disconnect", "foo") + assert result["result"] == "ok" def test_event_not_found_client(self): result = {} class MyNamespace(namespace.ClientNamespace): def on_custom_message(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) - ns = MyNamespace('/foo') + ns = MyNamespace("/foo") ns._set_client(mock.MagicMock()) - ns.trigger_event('another_custom_message', 'sid', {'data': 'data'}) + ns.trigger_event("another_custom_message", "sid", {"data": "data"}) assert result == {} def test_emit_client(self): - ns = namespace.ClientNamespace('/foo') + ns = namespace.ClientNamespace("/foo") ns._set_client(mock.MagicMock()) - ns.emit('ev', data='data', callback='cb') + ns.emit("ev", data="data", callback="cb") ns.client.emit.assert_called_with( - 'ev', data='data', namespace='/foo', callback='cb' + "ev", data="data", namespace="/foo", callback="cb" ) - ns.emit('ev', data='data', namespace='/bar', callback='cb') + ns.emit("ev", data="data", namespace="/bar", callback="cb") ns.client.emit.assert_called_with( - 'ev', data='data', namespace='/bar', callback='cb' + "ev", data="data", namespace="/bar", callback="cb" ) def test_send_client(self): - ns = namespace.ClientNamespace('/foo') + ns = namespace.ClientNamespace("/foo") ns._set_client(mock.MagicMock()) - ns.send(data='data', callback='cb') - ns.client.send.assert_called_with( - 'data', namespace='/foo', callback='cb' - ) - ns.send(data='data', namespace='/bar', callback='cb') - ns.client.send.assert_called_with( - 'data', namespace='/bar', callback='cb' - ) + ns.send(data="data", callback="cb") + ns.client.send.assert_called_with("data", namespace="/foo", callback="cb") + ns.send(data="data", namespace="/bar", callback="cb") + ns.client.send.assert_called_with("data", namespace="/bar", callback="cb") def test_call_client(self): - ns = namespace.ClientNamespace('/foo') + ns = namespace.ClientNamespace("/foo") ns._set_client(mock.MagicMock()) - ns.call('ev', data='data') + ns.call("ev", data="data") ns.client.call.assert_called_with( - 'ev', data='data', namespace='/foo', timeout=None) - ns.call('ev', data='data', namespace='/bar', timeout=45) + "ev", data="data", namespace="/foo", timeout=None + ) + ns.call("ev", data="data", namespace="/bar", timeout=45) ns.client.call.assert_called_with( - 'ev', data='data', namespace='/bar', timeout=45 + "ev", data="data", namespace="/bar", timeout=45 ) def test_disconnect_client(self): - ns = namespace.ClientNamespace('/foo') + ns = namespace.ClientNamespace("/foo") ns._set_client(mock.MagicMock()) ns.disconnect() ns.client.disconnect.assert_called_with() diff --git a/tests/common/test_packet.py b/tests/common/test_packet.py index 5682dab..b1ea4ec 100644 --- a/tests/common/test_packet.py +++ b/tests/common/test_packet.py @@ -11,130 +11,124 @@ class TestPacket: assert pkt.namespace is None assert pkt.id is None assert pkt.attachment_count == 0 - assert pkt.encode() == '2' + assert pkt.encode() == "2" def test_decode_default_packet(self): - pkt = packet.Packet(encoded_packet='2') - assert pkt.encode(), '2' + pkt = packet.Packet(encoded_packet="2") + assert pkt.encode(), "2" def test_encode_text_event_packet(self): - pkt = packet.Packet( - packet_type=packet.EVENT, data=['foo'] - ) + pkt = packet.Packet(packet_type=packet.EVENT, data=["foo"]) assert pkt.packet_type == packet.EVENT - assert pkt.data == ['foo'] + assert pkt.data == ["foo"] assert pkt.encode() == '2["foo"]' def test_decode_text_event_packet(self): pkt = packet.Packet(encoded_packet='2["foo"]') assert pkt.packet_type == packet.EVENT - assert pkt.data == ['foo'] + assert pkt.data == ["foo"] assert pkt.encode() == '2["foo"]' def test_decode_empty_event_packet(self): - pkt = packet.Packet(encoded_packet='1') + pkt = packet.Packet(encoded_packet="1") assert pkt.packet_type == packet.DISCONNECT # same thing, but with a numeric payload pkt = packet.Packet(encoded_packet=1) assert pkt.packet_type == packet.DISCONNECT def test_encode_binary_event_packet(self): - pkt = packet.Packet(packet_type=packet.EVENT, data=b'1234') + pkt = packet.Packet(packet_type=packet.EVENT, data=b"1234") assert pkt.packet_type == packet.BINARY_EVENT - assert pkt.data == b'1234' - a = ['51-{"_placeholder":true,"num":0}', b'1234'] - b = ['51-{"num":0,"_placeholder":true}', b'1234'] + assert pkt.data == b"1234" + a = ['51-{"_placeholder":true,"num":0}', b"1234"] + b = ['51-{"num":0,"_placeholder":true}', b"1234"] encoded_packet = pkt.encode() assert encoded_packet == a or encoded_packet == b def test_decode_binary_event_packet(self): pkt = packet.Packet(encoded_packet='51-{"_placeholder":true,"num":0}') - assert pkt.add_attachment(b'1234') + assert pkt.add_attachment(b"1234") assert pkt.packet_type == packet.BINARY_EVENT - assert pkt.data == b'1234' + assert pkt.data == b"1234" def test_encode_text_ack_packet(self): - pkt = packet.Packet( - packet_type=packet.ACK, data=['foo'] - ) + pkt = packet.Packet(packet_type=packet.ACK, data=["foo"]) assert pkt.packet_type == packet.ACK - assert pkt.data == ['foo'] + assert pkt.data == ["foo"] assert pkt.encode() == '3["foo"]' def test_decode_text_ack_packet(self): pkt = packet.Packet(encoded_packet='3["foo"]') assert pkt.packet_type == packet.ACK - assert pkt.data == ['foo'] + assert pkt.data == ["foo"] assert pkt.encode() == '3["foo"]' def test_encode_binary_ack_packet(self): - pkt = packet.Packet(packet_type=packet.ACK, data=b'1234') + pkt = packet.Packet(packet_type=packet.ACK, data=b"1234") assert pkt.packet_type == packet.BINARY_ACK - assert pkt.data == b'1234' - a = ['61-{"_placeholder":true,"num":0}', b'1234'] - b = ['61-{"num":0,"_placeholder":true}', b'1234'] + assert pkt.data == b"1234" + a = ['61-{"_placeholder":true,"num":0}', b"1234"] + b = ['61-{"num":0,"_placeholder":true}', b"1234"] encoded_packet = pkt.encode() assert encoded_packet == a or encoded_packet == b def test_decode_binary_ack_packet(self): pkt = packet.Packet(encoded_packet='61-{"_placeholder":true,"num":0}') - assert pkt.add_attachment(b'1234') + assert pkt.add_attachment(b"1234") assert pkt.packet_type == packet.BINARY_ACK - assert pkt.data == b'1234' + assert pkt.data == b"1234" def test_invalid_binary_packet(self): with pytest.raises(ValueError): - packet.Packet(packet_type=packet.CONNECT_ERROR, data=b'123') + packet.Packet(packet_type=packet.CONNECT_ERROR, data=b"123") def test_encode_namespace(self): pkt = packet.Packet( packet_type=packet.EVENT, - data=['foo'], - namespace='/bar', + data=["foo"], + namespace="/bar", ) - assert pkt.namespace == '/bar' + assert pkt.namespace == "/bar" assert pkt.encode() == '2/bar,["foo"]' def test_decode_namespace(self): pkt = packet.Packet(encoded_packet='2/bar,["foo"]') - assert pkt.namespace == '/bar' + assert pkt.namespace == "/bar" assert pkt.encode() == '2/bar,["foo"]' def test_decode_namespace_with_query_string(self): # some Socket.IO clients mistakenly attach the query string to the # namespace pkt = packet.Packet(encoded_packet='2/bar?a=b,["foo"]') - assert pkt.namespace == '/bar' + assert pkt.namespace == "/bar" assert pkt.encode() == '2/bar,["foo"]' def test_encode_namespace_no_data(self): - pkt = packet.Packet(packet_type=packet.EVENT, namespace='/bar') - assert pkt.encode() == '2/bar,' + pkt = packet.Packet(packet_type=packet.EVENT, namespace="/bar") + assert pkt.encode() == "2/bar," def test_decode_namespace_no_data(self): - pkt = packet.Packet(encoded_packet='2/bar,') - assert pkt.namespace == '/bar' + pkt = packet.Packet(encoded_packet="2/bar,") + assert pkt.namespace == "/bar" assert pkt.data is None - assert pkt.encode() == '2/bar,' + assert pkt.encode() == "2/bar," def test_encode_namespace_with_hyphens(self): pkt = packet.Packet( packet_type=packet.EVENT, - data=['foo'], - namespace='/b-a-r', + data=["foo"], + namespace="/b-a-r", ) - assert pkt.namespace == '/b-a-r' + assert pkt.namespace == "/b-a-r" assert pkt.encode() == '2/b-a-r,["foo"]' def test_decode_namespace_with_hyphens(self): pkt = packet.Packet(encoded_packet='2/b-a-r,["foo"]') - assert pkt.namespace == '/b-a-r' + assert pkt.namespace == "/b-a-r" assert pkt.encode() == '2/b-a-r,["foo"]' def test_encode_event_with_hyphens(self): - pkt = packet.Packet( - packet_type=packet.EVENT, data=['f-o-o'] - ) + pkt = packet.Packet(packet_type=packet.EVENT, data=["f-o-o"]) assert pkt.namespace is None assert pkt.encode() == '2["f-o-o"]' @@ -144,9 +138,7 @@ class TestPacket: assert pkt.encode() == '2["f-o-o"]' def test_encode_id(self): - pkt = packet.Packet( - packet_type=packet.EVENT, data=['foo'], id=123 - ) + pkt = packet.Packet(packet_type=packet.EVENT, data=["foo"], id=123) assert pkt.id == 123 assert pkt.encode() == '2123["foo"]' @@ -156,66 +148,66 @@ class TestPacket: assert pkt.encode() == '2123["foo"]' def test_decode_id_long(self): - pkt = packet.Packet(encoded_packet='2' + '1' * 100 + '["foo"]') - assert pkt.id == int('1' * 100) - assert pkt.data == ['foo'] + pkt = packet.Packet(encoded_packet="2" + "1" * 100 + '["foo"]') + assert pkt.id == int("1" * 100) + assert pkt.data == ["foo"] def test_decode_id_too_long(self): with pytest.raises(ValueError): - packet.Packet(encoded_packet='2' + '1' * 101) + packet.Packet(encoded_packet="2" + "1" * 101) with pytest.raises(ValueError): - packet.Packet(encoded_packet='2' + '1' * 101 + '["foo"]') + packet.Packet(encoded_packet="2" + "1" * 101 + '["foo"]') def test_encode_id_no_data(self): pkt = packet.Packet(packet_type=packet.EVENT, id=123) assert pkt.id == 123 assert pkt.data is None - assert pkt.encode() == '2123' + assert pkt.encode() == "2123" def test_decode_id_no_data(self): - pkt = packet.Packet(encoded_packet='2123') + pkt = packet.Packet(encoded_packet="2123") assert pkt.id == 123 assert pkt.data is None - assert pkt.encode() == '2123' + assert pkt.encode() == "2123" def test_encode_namespace_and_id(self): pkt = packet.Packet( packet_type=packet.EVENT, - data=['foo'], - namespace='/bar', + data=["foo"], + namespace="/bar", id=123, ) - assert pkt.namespace == '/bar' + assert pkt.namespace == "/bar" assert pkt.id == 123 assert pkt.encode() == '2/bar,123["foo"]' def test_decode_namespace_and_id(self): pkt = packet.Packet(encoded_packet='2/bar,123["foo"]') - assert pkt.namespace == '/bar' + assert pkt.namespace == "/bar" assert pkt.id == 123 assert pkt.encode() == '2/bar,123["foo"]' def test_encode_many_binary(self): pkt = packet.Packet( packet_type=packet.EVENT, - data={'a': '123', 'b': b'456', 'c': [b'789', 123]}, + data={"a": "123", "b": b"456", "c": [b"789", 123]}, ) assert pkt.packet_type == packet.BINARY_EVENT ep = pkt.encode() assert len(ep) == 3 - assert b'456' in ep - assert b'789' in ep + assert b"456" in ep + assert b"789" in ep def test_encode_many_binary_ack(self): pkt = packet.Packet( packet_type=packet.ACK, - data={'a': '123', 'b': b'456', 'c': [b'789', 123]}, + data={"a": "123", "b": b"456", "c": [b"789", 123]}, ) assert pkt.packet_type == packet.BINARY_ACK ep = pkt.encode() assert len(ep) == 3 - assert b'456' in ep - assert b'789' in ep + assert b"456" in ep + assert b"789" in ep def test_decode_many_binary(self): pkt = packet.Packet( @@ -224,12 +216,12 @@ class TestPacket: '"c":[{"_placeholder":true,"num":1},123]}' ) ) - assert not pkt.add_attachment(b'456') - assert pkt.add_attachment(b'789') + assert not pkt.add_attachment(b"456") + assert pkt.add_attachment(b"789") assert pkt.packet_type == packet.BINARY_EVENT - assert pkt.data['a'] == '123' - assert pkt.data['b'] == b'456' - assert pkt.data['c'] == [b'789', 123] + assert pkt.data["a"] == "123" + assert pkt.data["b"] == b"456" + assert pkt.data["c"] == [b"789", 123] def test_decode_many_binary_ack(self): pkt = packet.Packet( @@ -238,12 +230,12 @@ class TestPacket: '"c":[{"_placeholder":true,"num":1},123]}' ) ) - assert not pkt.add_attachment(b'456') - assert pkt.add_attachment(b'789') + assert not pkt.add_attachment(b"456") + assert pkt.add_attachment(b"789") assert pkt.packet_type == packet.BINARY_ACK - assert pkt.data['a'] == '123' - assert pkt.data['b'] == b'456' - assert pkt.data['c'] == [b'789', 123] + assert pkt.data["a"] == "123" + assert pkt.data["b"] == b"456" + assert pkt.data["c"] == [b"789", 123] def test_decode_too_many_binary_packets(self): pkt = packet.Packet( @@ -252,14 +244,14 @@ class TestPacket: '"c":[{"_placeholder":true,"num":1},123]}' ) ) - assert not pkt.add_attachment(b'456') - assert pkt.add_attachment(b'789') + assert not pkt.add_attachment(b"456") + assert pkt.add_attachment(b"789") with pytest.raises(ValueError): - pkt.add_attachment(b'123') + pkt.add_attachment(b"123") def test_decode_attachment_count_too_long(self): with pytest.raises(ValueError): - packet.Packet(encoded_packet='6' + ('1' * 11) + '-{"a":"123"}') + packet.Packet(encoded_packet="6" + ("1" * 11) + '-{"a":"123"}') def test_decode_dash_in_payload(self): pkt = packet.Packet(encoded_packet='6{"a":"0123456789-"}') @@ -268,14 +260,14 @@ class TestPacket: def test_data_is_binary_list(self): pkt = packet.Packet() - assert not pkt._data_is_binary(['foo']) + 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 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({"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 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 6d8eda7..2065086 100644 --- a/tests/common/test_pubsub_manager.py +++ b/tests/common/test_pubsub_manager.py @@ -4,9 +4,7 @@ from unittest import mock import pytest -from socketio import manager -from socketio import pubsub_manager -from socketio import packet +from socketio import manager, packet, pubsub_manager class TestPubSubManager: @@ -24,18 +22,16 @@ class TestPubSubManager: self.pm = pubsub_manager.PubSubManager() self.pm._publish = mock.MagicMock() self.pm.set_server(mock_server) - self.pm.host_id = '123456' + self.pm.host_id = "123456" self.pm.initialize() def test_default_init(self): - assert self.pm.channel == 'socketio' - self.pm.server.start_background_task.assert_called_once_with( - self.pm._thread - ) + assert self.pm.channel == "socketio" + self.pm.server.start_background_task.assert_called_once_with(self.pm._thread) def test_custom_init(self): - pubsub = pubsub_manager.PubSubManager(channel='foo') - assert pubsub.channel == 'foo' + pubsub = pubsub_manager.PubSubManager(channel="foo") + assert pubsub.channel == "foo" assert len(pubsub.host_id) == 32 def test_write_only_init(self): @@ -43,206 +39,226 @@ class TestPubSubManager: pm = pubsub_manager.PubSubManager(write_only=True) pm.set_server(mock_server) pm.initialize() - assert pm.channel == 'socketio' + assert pm.channel == "socketio" assert len(pm.host_id) == 32 assert pm.server.start_background_task.call_count == 0 def test_write_only_default_logger(self): pm = pubsub_manager.PubSubManager(write_only=True) pm.initialize() - assert pm.channel == 'socketio' + assert pm.channel == "socketio" assert len(pm.host_id) == 32 - assert pm._get_logger() == logging.getLogger('socketio') + assert pm._get_logger() == logging.getLogger("socketio") def test_write_only_with_provided_logger(self): - test_logger = logging.getLogger('new_logger') + test_logger = logging.getLogger("new_logger") pm = pubsub_manager.PubSubManager(write_only=True, logger=test_logger) pm.initialize() - assert pm.channel == 'socketio' + assert pm.channel == "socketio" assert len(pm.host_id) == 32 assert pm._get_logger() == test_logger def test_emit(self): - self.pm.emit('foo', 'bar') + self.pm.emit("foo", "bar") self.pm._publish.assert_called_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/', - 'room': None, - 'skip_sid': None, - 'callback': None, - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "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) + self.pm.emit("foo", "bar", to=sid) self.pm._publish.assert_called_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/', - 'room': sid, - 'skip_sid': None, - 'callback': None, - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "namespace": "/", + "room": sid, + "skip_sid": None, + "callback": None, + "host_id": "123456", } ) def test_emit_with_namespace(self): - self.pm.emit('foo', 'bar', namespace='/baz') + self.pm.emit("foo", "bar", namespace="/baz") self.pm._publish.assert_called_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/baz', - 'room': None, - 'skip_sid': None, - 'callback': None, - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "namespace": "/baz", + "room": None, + "skip_sid": None, + "callback": None, + "host_id": "123456", } ) def test_emit_with_room(self): - self.pm.emit('foo', 'bar', room='baz') + self.pm.emit("foo", "bar", room="baz") self.pm._publish.assert_called_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/', - 'room': 'baz', - 'skip_sid': None, - 'callback': None, - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "namespace": "/", + "room": "baz", + "skip_sid": None, + "callback": None, + "host_id": "123456", } ) def test_emit_with_skip_sid(self): - self.pm.emit('foo', 'bar', skip_sid='baz') + self.pm.emit("foo", "bar", skip_sid="baz") self.pm._publish.assert_called_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/', - 'room': None, - 'skip_sid': 'baz', - 'callback': None, - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "namespace": "/", + "room": None, + "skip_sid": "baz", + "callback": None, + "host_id": "123456", } ) def test_emit_with_callback(self): - with mock.patch.object( - self.pm, '_generate_ack_id', return_value='123' - ): - self.pm.emit('foo', 'bar', room='baz', callback='cb') + with mock.patch.object(self.pm, "_generate_ack_id", return_value="123"): + self.pm.emit("foo", "bar", room="baz", callback="cb") self.pm._publish.assert_called_once_with( { - 'method': 'emit', - 'event': 'foo', - 'data': 'bar', - 'namespace': '/', - 'room': 'baz', - 'skip_sid': None, - 'callback': ('baz', '/', '123'), - 'host_id': '123456', + "method": "emit", + "event": "foo", + "data": "bar", + "namespace": "/", + "room": "baz", + "skip_sid": None, + "callback": ("baz", "/", "123"), + "host_id": "123456", } ) def test_emit_with_callback_without_server(self): standalone_pm = pubsub_manager.PubSubManager() with pytest.raises(RuntimeError): - standalone_pm.emit('foo', 'bar', callback='cb') + standalone_pm.emit("foo", "bar", callback="cb") def test_emit_with_callback_missing_room(self): - with mock.patch.object( - self.pm, '_generate_ack_id', return_value='123' - ): + with mock.patch.object(self.pm, "_generate_ack_id", return_value="123"): with pytest.raises(ValueError): - self.pm.emit('foo', 'bar', callback='cb') + self.pm.emit("foo", "bar", callback="cb") def test_emit_with_ignore_queue(self): - sid = self.pm.connect('123', '/') - self.pm.emit( - 'foo', 'bar', room=sid, namespace='/', ignore_queue=True - ) + sid = self.pm.connect("123", "/") + self.pm.emit("foo", "bar", room=sid, namespace="/", ignore_queue=True) self.pm._publish.assert_not_called() assert self.pm.server._send_eio_packet.call_count == 1 - assert self.pm.server._send_eio_packet.call_args_list[0][0][0] == '123' + assert self.pm.server._send_eio_packet.call_args_list[0][0][0] == "123" pkt = self.pm.server._send_eio_packet.call_args_list[0][0][1] assert pkt.encode() == '42["foo","bar"]' def test_can_disconnect(self): - sid = self.pm.connect('123', '/') - assert self.pm.can_disconnect(sid, '/') - self.pm.can_disconnect(sid, '/foo') + sid = self.pm.connect("123", "/") + assert self.pm.can_disconnect(sid, "/") + self.pm.can_disconnect(sid, "/foo") self.pm._publish.assert_called_once_with( - {'method': 'disconnect', 'sid': sid, 'namespace': '/foo', - 'host_id': '123456'} + { + "method": "disconnect", + "sid": sid, + "namespace": "/foo", + "host_id": "123456", + } ) def test_disconnect(self): - self.pm.disconnect('foo') + self.pm.disconnect("foo") self.pm._publish.assert_called_once_with( - {'method': 'disconnect', 'sid': 'foo', 'namespace': '/', - 'host_id': '123456'} + { + "method": "disconnect", + "sid": "foo", + "namespace": "/", + "host_id": "123456", + } ) def test_disconnect_ignore_queue(self): - sid = self.pm.connect('123', '/') - self.pm.pre_disconnect(sid, '/') + sid = self.pm.connect("123", "/") + self.pm.pre_disconnect(sid, "/") self.pm.disconnect(sid, ignore_queue=True) self.pm._publish.assert_not_called() - assert not self.pm.is_connected(sid, '/') + assert not self.pm.is_connected(sid, "/") def test_enter_room(self): - sid = self.pm.connect('123', '/') - self.pm.enter_room(sid, '/', 'foo') - self.pm.enter_room('456', '/', 'foo') - assert sid in self.pm.rooms['/']['foo'] - assert self.pm.rooms['/']['foo'][sid] == '123' + sid = self.pm.connect("123", "/") + self.pm.enter_room(sid, "/", "foo") + self.pm.enter_room("456", "/", "foo") + assert sid in self.pm.rooms["/"]["foo"] + assert self.pm.rooms["/"]["foo"][sid] == "123" self.pm._publish.assert_called_once_with( - {'method': 'enter_room', 'sid': '456', 'room': 'foo', - 'namespace': '/', 'host_id': '123456'} + { + "method": "enter_room", + "sid": "456", + "room": "foo", + "namespace": "/", + "host_id": "123456", + } ) def test_leave_room(self): - sid = self.pm.connect('123', '/') - self.pm.leave_room(sid, '/', 'foo') - self.pm.leave_room('456', '/', 'foo') - assert 'foo' not in self.pm.rooms['/'] + sid = self.pm.connect("123", "/") + self.pm.leave_room(sid, "/", "foo") + self.pm.leave_room("456", "/", "foo") + assert "foo" not in self.pm.rooms["/"] self.pm._publish.assert_called_once_with( - {'method': 'leave_room', 'sid': '456', 'room': 'foo', - 'namespace': '/', 'host_id': '123456'} + { + "method": "leave_room", + "sid": "456", + "room": "foo", + "namespace": "/", + "host_id": "123456", + } ) def test_close_room(self): - self.pm.close_room('foo') + self.pm.close_room("foo") self.pm._publish.assert_called_once_with( - {'method': 'close_room', 'room': 'foo', 'namespace': '/', - 'host_id': '123456'} + { + "method": "close_room", + "room": "foo", + "namespace": "/", + "host_id": "123456", + } ) def test_close_room_with_namespace(self): - self.pm.close_room('foo', '/bar') + self.pm.close_room("foo", "/bar") self.pm._publish.assert_called_once_with( - {'method': 'close_room', 'room': 'foo', 'namespace': '/bar', - 'host_id': '123456'} + { + "method": "close_room", + "room": "foo", + "namespace": "/bar", + "host_id": "123456", + } ) def test_handle_emit(self): - with mock.patch.object(manager.Manager, 'emit') as super_emit: - self.pm._handle_emit({'event': 'foo', 'data': 'bar'}) + 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', + "foo", + "bar", namespace=None, room=None, skip_sid=None, @@ -250,210 +266,186 @@ class TestPubSubManager: ) def test_handle_emit_with_namespace(self): - with mock.patch.object(manager.Manager, 'emit') as super_emit: - self.pm._handle_emit( - {'event': 'foo', 'data': 'bar', 'namespace': '/baz'} - ) + with mock.patch.object(manager.Manager, "emit") as super_emit: + self.pm._handle_emit({"event": "foo", "data": "bar", "namespace": "/baz"}) super_emit.assert_called_once_with( - 'foo', - 'bar', - namespace='/baz', + "foo", + "bar", + namespace="/baz", room=None, skip_sid=None, callback=None, ) def test_handle_emit_with_room(self): - with mock.patch.object(manager.Manager, 'emit') as super_emit: - self.pm._handle_emit( - {'event': 'foo', 'data': 'bar', 'room': 'baz'} - ) + with mock.patch.object(manager.Manager, "emit") as super_emit: + self.pm._handle_emit({"event": "foo", "data": "bar", "room": "baz"}) super_emit.assert_called_once_with( - 'foo', - 'bar', + "foo", + "bar", namespace=None, - room='baz', + room="baz", skip_sid=None, callback=None, ) def test_handle_emit_with_skip_sid(self): - with mock.patch.object(manager.Manager, 'emit') as super_emit: - self.pm._handle_emit( - {'event': 'foo', 'data': 'bar', 'skip_sid': '123'} - ) + with mock.patch.object(manager.Manager, "emit") as super_emit: + self.pm._handle_emit({"event": "foo", "data": "bar", "skip_sid": "123"}) super_emit.assert_called_once_with( - 'foo', - 'bar', + "foo", + "bar", namespace=None, room=None, - skip_sid='123', + skip_sid="123", callback=None, ) def test_handle_emit_with_remote_callback(self): - with mock.patch.object(manager.Manager, 'emit') as super_emit: + with mock.patch.object(manager.Manager, "emit") as super_emit: self.pm._handle_emit( { - 'event': 'foo', - 'data': 'bar', - 'namespace': '/baz', - 'callback': ('sid', '/baz', 123), - 'host_id': 'x', + "event": "foo", + "data": "bar", + "namespace": "/baz", + "callback": ("sid", "/baz", 123), + "host_id": "x", } ) assert super_emit.call_count == 1 - assert super_emit.call_args[0] == ('foo', 'bar') - assert super_emit.call_args[1]['namespace'] == '/baz' - assert super_emit.call_args[1]['room'] is None - assert super_emit.call_args[1]['skip_sid'] is None - assert isinstance( - super_emit.call_args[1]['callback'], functools.partial - ) - super_emit.call_args[1]['callback']('one', 2, 'three') + assert super_emit.call_args[0] == ("foo", "bar") + assert super_emit.call_args[1]["namespace"] == "/baz" + assert super_emit.call_args[1]["room"] is None + assert super_emit.call_args[1]["skip_sid"] is None + assert isinstance(super_emit.call_args[1]["callback"], functools.partial) + super_emit.call_args[1]["callback"]("one", 2, "three") self.pm._publish.assert_called_once_with( { - 'method': 'callback', - 'host_id': 'x', - 'sid': 'sid', - 'namespace': '/baz', - 'id': 123, - 'args': ('one', 2, 'three'), + "method": "callback", + "host_id": "x", + "sid": "sid", + "namespace": "/baz", + "id": 123, + "args": ("one", 2, "three"), } ) def test_handle_emit_with_local_callback(self): - with mock.patch.object(manager.Manager, 'emit') as super_emit: + with mock.patch.object(manager.Manager, "emit") as super_emit: self.pm._handle_emit( { - 'event': 'foo', - 'data': 'bar', - 'namespace': '/baz', - 'callback': ('sid', '/baz', 123), - 'host_id': self.pm.host_id, + "event": "foo", + "data": "bar", + "namespace": "/baz", + "callback": ("sid", "/baz", 123), + "host_id": self.pm.host_id, } ) assert super_emit.call_count == 1 - assert super_emit.call_args[0] == ('foo', 'bar') - assert super_emit.call_args[1]['namespace'] == '/baz' - assert super_emit.call_args[1]['room'] is None - assert super_emit.call_args[1]['skip_sid'] is None - assert isinstance( - super_emit.call_args[1]['callback'], functools.partial - ) - super_emit.call_args[1]['callback']('one', 2, 'three') + assert super_emit.call_args[0] == ("foo", "bar") + assert super_emit.call_args[1]["namespace"] == "/baz" + assert super_emit.call_args[1]["room"] is None + assert super_emit.call_args[1]["skip_sid"] is None + assert isinstance(super_emit.call_args[1]["callback"], functools.partial) + super_emit.call_args[1]["callback"]("one", 2, "three") self.pm._publish.assert_not_called() def test_handle_callback(self): host_id = self.pm.host_id - with mock.patch.object(self.pm, 'trigger_callback') as trigger: + with mock.patch.object(self.pm, "trigger_callback") as trigger: self.pm._handle_callback( { - 'method': 'callback', - 'host_id': host_id, - 'sid': 'sid', - 'namespace': '/', - 'id': 123, - 'args': ('one', 2), + "method": "callback", + "host_id": host_id, + "sid": "sid", + "namespace": "/", + "id": 123, + "args": ("one", 2), } ) - trigger.assert_called_once_with('sid', 123, ('one', 2)) + trigger.assert_called_once_with("sid", 123, ("one", 2)) def test_handle_callback_bad_host_id(self): - with mock.patch.object(self.pm, 'trigger_callback') as trigger: + with mock.patch.object(self.pm, "trigger_callback") as trigger: self.pm._handle_callback( { - 'method': 'callback', - 'host_id': 'bad', - 'sid': 'sid', - 'namespace': '/', - 'id': 123, - 'args': ('one', 2), + "method": "callback", + "host_id": "bad", + "sid": "sid", + "namespace": "/", + "id": 123, + "args": ("one", 2), } ) assert trigger.call_count == 0 def test_handle_callback_missing_args(self): host_id = self.pm.host_id - with mock.patch.object(self.pm, 'trigger_callback') as trigger: + with mock.patch.object(self.pm, "trigger_callback") as trigger: self.pm._handle_callback( { - 'method': 'callback', - 'host_id': host_id, - 'sid': 'sid', - 'namespace': '/', - 'id': 123, + "method": "callback", + "host_id": host_id, + "sid": "sid", + "namespace": "/", + "id": 123, } ) self.pm._handle_callback( { - 'method': 'callback', - 'host_id': host_id, - 'sid': 'sid', - 'namespace': '/', + "method": "callback", + "host_id": host_id, + "sid": "sid", + "namespace": "/", } ) self.pm._handle_callback( - {'method': 'callback', 'host_id': host_id, 'sid': 'sid'} - ) - self.pm._handle_callback( - {'method': 'callback', 'host_id': host_id} + {"method": "callback", "host_id": host_id, "sid": "sid"} ) + self.pm._handle_callback({"method": "callback", "host_id": host_id}) assert trigger.call_count == 0 def test_handle_disconnect(self): self.pm._handle_disconnect( - {'method': 'disconnect', 'sid': '123', 'namespace': '/foo'} + {"method": "disconnect", "sid": "123", "namespace": "/foo"} ) self.pm.server.disconnect.assert_called_once_with( - sid='123', namespace='/foo', ignore_queue=True + sid="123", namespace="/foo", ignore_queue=True ) def test_handle_enter_room(self): - sid = self.pm.connect('123', '/') - with mock.patch.object( - manager.Manager, 'enter_room' - ) as super_enter_room: - self.pm._handle_enter_room({ - 'method': 'enter_room', 'sid': sid, 'namespace': '/', - 'room': 'foo'}) - self.pm._handle_enter_room({ - 'method': 'enter_room', 'sid': '456', 'namespace': '/', - 'room': 'foo'}) - super_enter_room.assert_called_once_with(sid, '/', 'foo') + sid = self.pm.connect("123", "/") + with mock.patch.object(manager.Manager, "enter_room") as super_enter_room: + self.pm._handle_enter_room( + {"method": "enter_room", "sid": sid, "namespace": "/", "room": "foo"} + ) + self.pm._handle_enter_room( + {"method": "enter_room", "sid": "456", "namespace": "/", "room": "foo"} + ) + super_enter_room.assert_called_once_with(sid, "/", "foo") def test_handle_leave_room(self): - sid = self.pm.connect('123', '/') - with mock.patch.object( - manager.Manager, 'leave_room' - ) as super_leave_room: - self.pm._handle_leave_room({ - 'method': 'leave_room', 'sid': sid, 'namespace': '/', - 'room': 'foo'}) - self.pm._handle_leave_room({ - 'method': 'leave_room', 'sid': '456', 'namespace': '/', - 'room': 'foo'}) - super_leave_room.assert_called_once_with(sid, '/', 'foo') + sid = self.pm.connect("123", "/") + with mock.patch.object(manager.Manager, "leave_room") as super_leave_room: + self.pm._handle_leave_room( + {"method": "leave_room", "sid": sid, "namespace": "/", "room": "foo"} + ) + self.pm._handle_leave_room( + {"method": "leave_room", "sid": "456", "namespace": "/", "room": "foo"} + ) + super_leave_room.assert_called_once_with(sid, "/", "foo") def test_handle_close_room(self): - with mock.patch.object( - manager.Manager, 'close_room' - ) as super_close_room: - self.pm._handle_close_room({'method': 'close_room', 'room': 'foo'}) - super_close_room.assert_called_once_with( - room='foo', namespace=None - ) + with mock.patch.object(manager.Manager, "close_room") as super_close_room: + self.pm._handle_close_room({"method": "close_room", "room": "foo"}) + super_close_room.assert_called_once_with(room="foo", namespace=None) def test_handle_close_room_with_namespace(self): - with mock.patch.object( - manager.Manager, 'close_room' - ) as super_close_room: + with mock.patch.object(manager.Manager, "close_room") as super_close_room: self.pm._handle_close_room( - {'method': 'close_room', 'room': 'foo', 'namespace': '/bar'} - ) - super_close_room.assert_called_once_with( - room='foo', namespace='/bar' + {"method": "close_room", "room": "foo", "namespace": "/bar"} ) + super_close_room.assert_called_once_with(room="foo", namespace="/bar") def test_background_thread(self): self.pm._handle_emit = mock.MagicMock() @@ -467,29 +459,47 @@ class TestPubSubManager: def messages(): import pickle - yield {'method': 'emit', 'value': 'foo', 'host_id': 'x'} - yield {'missing': 'method', 'host_id': 'x'} + 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 {'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 { + "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 { + "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" # these should not publish anything on the queue, as they come from # the same host - yield {'method': 'emit', 'value': 'foo', 'host_id': host_id} - 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 {"method": "emit", "value": "foo", "host_id": host_id} + 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} + ) self.pm._listen = mock.MagicMock(side_effect=messages) try: @@ -498,36 +508,45 @@ class TestPubSubManager: pass self.pm._handle_emit.assert_called_once_with( - {'method': 'emit', 'value': 'foo', 'host_id': 'x'} + {"method": "emit", "value": "foo", "host_id": "x"} ) self.pm._handle_callback.assert_any_call( - {'method': 'callback', 'value': 'bar', 'host_id': 'x'} + {"method": "callback", "value": "bar", "host_id": "x"} ) self.pm._handle_callback.assert_any_call( - {'method': 'callback', 'value': 'bar', 'host_id': host_id} + {"method": "callback", "value": "bar", "host_id": host_id} ) self.pm._handle_disconnect.assert_called_once_with( - {'method': 'disconnect', 'sid': '123', 'namespace': '/foo', - 'host_id': 'x'} + {"method": "disconnect", "sid": "123", "namespace": "/foo", "host_id": "x"} ) self.pm._handle_enter_room.assert_called_once_with( - {'method': 'enter_room', 'sid': '123', 'namespace': '/foo', - 'room': 'room', 'host_id': 'x'} + { + "method": "enter_room", + "sid": "123", + "namespace": "/foo", + "room": "room", + "host_id": "x", + } ) self.pm._handle_leave_room.assert_called_once_with( - {'method': 'leave_room', 'sid': '123', 'namespace': '/foo', - 'room': 'room', 'host_id': 'x'} + { + "method": "leave_room", + "sid": "123", + "namespace": "/foo", + "room": "room", + "host_id": "x", + } ) self.pm._handle_close_room.assert_called_once_with( - {'method': 'close_room', 'value': 'baz', 'host_id': 'x'} + {"method": "close_room", "value": "baz", "host_id": "x"} ) def test_background_thread_exception(self): self.pm._handle_emit = mock.MagicMock(side_effect=[ValueError(), None]) def messages(): - yield {'method': 'emit', 'value': 'foo', 'host_id': 'x'} - yield {'method': 'emit', 'value': 'bar', 'host_id': 'x'} + yield {"method": "emit", "value": "foo", "host_id": "x"} + yield {"method": "emit", "value": "bar", "host_id": "x"} self.pm._listen = mock.MagicMock(side_effect=messages) try: @@ -536,8 +555,8 @@ class TestPubSubManager: pass self.pm._handle_emit.assert_any_call( - {'method': 'emit', 'value': 'foo', 'host_id': 'x'} + {"method": "emit", "value": "foo", "host_id": "x"} ) self.pm._handle_emit.assert_called_with( - {'method': 'emit', 'value': 'bar', 'host_id': 'x'} + {"method": "emit", "value": "bar", "host_id": "x"} ) diff --git a/tests/common/test_redis_manager.py b/tests/common/test_redis_manager.py index 3e5ee1e..aad3925 100644 --- a/tests/common/test_redis_manager.py +++ b/tests/common/test_redis_manager.py @@ -6,33 +6,25 @@ 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') + parse_redis_sentinel_url("redis://localhost:6379/0") - assert parse_redis_sentinel_url( - 'redis+sentinel://localhost:6379' - ) == ( - [('localhost', 6379)], + 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)], + "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} + {"db": 0}, ) assert parse_redis_sentinel_url( - 'redis+sentinel://user:password@h1:6379,h2:6379,h1:6380/0/myredis' + "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} + [("h1", 6379), ("h2", 6379), ("h1", 6380)], + "myredis", + {"username": "user", "password": "password", "db": 0}, ) diff --git a/tests/common/test_server.py b/tests/common/test_server.py index 445d5d9..a8cc5bf 100644 --- a/tests/common/test_server.py +++ b/tests/common/test_server.py @@ -1,19 +1,16 @@ import logging from unittest import mock -from engineio import json -from engineio import packet as eio_packet import pytest +from engineio import json, packet as eio_packet -from socketio import exceptions -from socketio import msgpack_packet -from socketio import namespace -from socketio import packet -from socketio import server +from socketio import exceptions, msgpack_packet, namespace, packet, server -@mock.patch('socketio.server.engineio.Server', **{ - 'return_value.generate_id.side_effect': [str(i) for i in range(1, 10)]}) +@mock.patch( + "socketio.server.engineio.Server", + **{"return_value.generate_id.side_effect": [str(i) for i in range(1, 10)]}, +) class TestServer: def teardown_method(self): # restore JSON encoder, in case a test changed it @@ -21,12 +18,10 @@ class TestServer: def test_create(self, eio): mgr = mock.MagicMock() - s = server.Server( - client_manager=mgr, async_handlers=True, foo='bar' - ) + s = server.Server(client_manager=mgr, async_handlers=True, foo="bar") s.handle_request({}, None) s.handle_request({}, None) - eio.assert_called_once_with(**{'foo': 'bar', 'async_handlers': False}) + 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 @@ -35,19 +30,19 @@ class TestServer: def test_on_event(self, eio): s = server.Server() - @s.on('connect') + @s.on("connect") def foo(): pass def bar(reason): pass - s.on('disconnect', bar) - s.on('disconnect', bar, namespace='/foo') + s.on("disconnect", bar) + s.on("disconnect", bar, namespace="/foo") - assert s.handlers['/']['connect'] == foo - assert s.handlers['/']['disconnect'] == bar - assert s.handlers['/foo']['disconnect'] == bar + assert s.handlers["/"]["connect"] == foo + assert s.handlers["/"]["disconnect"] == bar + assert s.handlers["/foo"]["disconnect"] == bar def test_event(self, eio): s = server.Server() @@ -64,51 +59,51 @@ class TestServer: def bar(): pass - @s.event(namespace='/foo') + @s.event(namespace="/foo") def disconnect(): pass - assert s.handlers['/']['connect'] == connect - assert s.handlers['/']['foo'] == foo - assert s.handlers['/']['bar'] == bar - assert s.handlers['/foo']['disconnect'] == disconnect + assert s.handlers["/"]["connect"] == connect + assert s.handlers["/"]["foo"] == foo + assert s.handlers["/"]["bar"] == bar + assert s.handlers["/foo"]["disconnect"] == disconnect def test_emit(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) s.emit( - 'my event', - {'foo': 'bar'}, - to='room', - skip_sid='123', - namespace='/foo', - callback='cb', + "my event", + {"foo": "bar"}, + to="room", + skip_sid="123", + namespace="/foo", + callback="cb", ) s.manager.emit.assert_called_once_with( - 'my event', - {'foo': 'bar'}, - '/foo', - room='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + "/foo", + room="room", + skip_sid="123", + callback="cb", ignore_queue=False, ) s.emit( - 'my event', - {'foo': 'bar'}, - room='room', - skip_sid='123', - namespace='/foo', - callback='cb', + "my event", + {"foo": "bar"}, + room="room", + skip_sid="123", + namespace="/foo", + callback="cb", ignore_queue=True, ) s.manager.emit.assert_called_with( - 'my event', - {'foo': 'bar'}, - '/foo', - room='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + "/foo", + room="room", + skip_sid="123", + callback="cb", ignore_queue=True, ) @@ -116,68 +111,67 @@ class TestServer: mgr = mock.MagicMock() s = server.Server(client_manager=mgr) s.emit( - 'my event', - {'foo': 'bar'}, - to='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + to="room", + skip_sid="123", + callback="cb", ) s.manager.emit.assert_called_once_with( - 'my event', - {'foo': 'bar'}, - '/', - room='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + "/", + room="room", + skip_sid="123", + callback="cb", ignore_queue=False, ) s.emit( - 'my event', - {'foo': 'bar'}, - room='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + room="room", + skip_sid="123", + callback="cb", ignore_queue=True, ) s.manager.emit.assert_called_with( - 'my event', - {'foo': 'bar'}, - '/', - room='room', - skip_sid='123', - callback='cb', + "my event", + {"foo": "bar"}, + "/", + room="room", + skip_sid="123", + callback="cb", ignore_queue=True, ) def test_send(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) - s.send( - 'foo', to='room', skip_sid='123', namespace='/foo', callback='cb' - ) + s.send("foo", to="room", skip_sid="123", namespace="/foo", callback="cb") s.manager.emit.assert_called_once_with( - 'message', - 'foo', - '/foo', - room='room', - skip_sid='123', - callback='cb', + "message", + "foo", + "/foo", + room="room", + skip_sid="123", + callback="cb", ignore_queue=False, ) s.send( - 'foo', room='room', - skip_sid='123', - namespace='/foo', - callback='cb', + "foo", + room="room", + skip_sid="123", + namespace="/foo", + callback="cb", ignore_queue=True, ) s.manager.emit.assert_called_with( - 'message', - 'foo', - '/foo', - room='room', - skip_sid='123', - callback='cb', + "message", + "foo", + "/foo", + room="room", + skip_sid="123", + callback="cb", ignore_queue=True, ) @@ -187,11 +181,11 @@ class TestServer: def fake_event_wait(timeout=None): assert timeout == 60 - s.manager.emit.call_args_list[0][1]['callback']('foo', 321) + s.manager.emit.call_args_list[0][1]["callback"]("foo", 321) return True s.eio.create_event.return_value.wait = fake_event_wait - assert s.call('foo', sid='123') == ('foo', 321) + assert s.call("foo", sid="123") == ("foo", 321) def test_call_with_timeout(self, eio): mgr = mock.MagicMock() @@ -203,634 +197,623 @@ class TestServer: s.eio.create_event.return_value.wait = fake_event_wait with pytest.raises(exceptions.TimeoutError): - s.call('foo', sid='123', timeout=12) + s.call("foo", sid="123", timeout=12) def test_call_with_broadcast(self, eio): s = server.Server() with pytest.raises(ValueError): - s.call('foo') + s.call("foo") def test_call_without_async_handlers(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr, async_handlers=False) with pytest.raises(RuntimeError): - s.call('foo', sid='123', timeout=12) + s.call("foo", sid="123", timeout=12) def test_enter_room(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) - s.enter_room('123', 'room', namespace='/foo') - s.manager.enter_room.assert_called_once_with('123', '/foo', 'room') + s.enter_room("123", "room", namespace="/foo") + s.manager.enter_room.assert_called_once_with("123", "/foo", "room") def test_enter_room_default_namespace(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) - s.enter_room('123', 'room') - s.manager.enter_room.assert_called_once_with('123', '/', 'room') + s.enter_room("123", "room") + s.manager.enter_room.assert_called_once_with("123", "/", "room") def test_leave_room(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) - s.leave_room('123', 'room', namespace='/foo') - s.manager.leave_room.assert_called_once_with('123', '/foo', 'room') + s.leave_room("123", "room", namespace="/foo") + s.manager.leave_room.assert_called_once_with("123", "/foo", "room") def test_leave_room_default_namespace(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) - s.leave_room('123', 'room') - s.manager.leave_room.assert_called_once_with('123', '/', 'room') + s.leave_room("123", "room") + s.manager.leave_room.assert_called_once_with("123", "/", "room") def test_close_room(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) - s.close_room('room', namespace='/foo') - s.manager.close_room.assert_called_once_with('room', '/foo') + s.close_room("room", namespace="/foo") + s.manager.close_room.assert_called_once_with("room", "/foo") def test_close_room_default_namespace(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) - s.close_room('room') - s.manager.close_room.assert_called_once_with('room', '/') + s.close_room("room") + s.manager.close_room.assert_called_once_with("room", "/") def test_rooms(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) - s.rooms('123', namespace='/foo') - s.manager.get_rooms.assert_called_once_with('123', '/foo') + s.rooms("123", namespace="/foo") + s.manager.get_rooms.assert_called_once_with("123", "/foo") def test_rooms_default_namespace(self, eio): mgr = mock.MagicMock() s = server.Server(client_manager=mgr) - s.rooms('123') - s.manager.get_rooms.assert_called_once_with('123', '/') + s.rooms("123") + s.manager.get_rooms.assert_called_once_with("123", "/") def test_handle_request(self, eio): s = server.Server() - s.handle_request('environ', 'start_response') - s.eio.handle_request.assert_called_once_with( - 'environ', 'start_response' - ) + s.handle_request("environ", "start_response") + s.eio.handle_request.assert_called_once_with("environ", "start_response") def test_send_packet(self, eio): s = server.Server() - s._send_packet('123', packet.Packet( - packet.EVENT, ['my event', 'my data'], namespace='/foo')) - s.eio.send.assert_called_once_with( - '123', '2/foo,["my event","my data"]' + s._send_packet( + "123", + packet.Packet(packet.EVENT, ["my event", "my data"], namespace="/foo"), ) + s.eio.send.assert_called_once_with("123", '2/foo,["my event","my data"]') def test_send_eio_packet(self, eio): s = server.Server() - s._send_eio_packet('123', eio_packet.Packet( - eio_packet.MESSAGE, 'hello')) + s._send_eio_packet("123", eio_packet.Packet(eio_packet.MESSAGE, "hello")) assert s.eio.send_packet.call_count == 1 - assert s.eio.send_packet.call_args_list[0][0][0] == '123' + assert s.eio.send_packet.call_args_list[0][0][0] == "123" pkt = s.eio.send_packet.call_args_list[0][0][1] - assert pkt.encode() == '4hello' + assert pkt.encode() == "4hello" def test_transport(self, eio): s = server.Server() - s.eio.transport = mock.MagicMock(return_value='polling') - sid_foo = s.manager.connect('123', '/foo') - assert s.transport(sid_foo, '/foo') == 'polling' - s.eio.transport.assert_called_once_with('123') + s.eio.transport = mock.MagicMock(return_value="polling") + sid_foo = 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): s = server.Server() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock() - s.on('connect', handler) - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - assert s.manager.is_connected('1', '/') - handler.assert_called_once_with('1', 'environ') - s.eio.send.assert_called_once_with('123', '0{"sid":"1"}') + s.on("connect", handler) + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + assert s.manager.is_connected("1", "/") + handler.assert_called_once_with("1", "environ") + s.eio.send.assert_called_once_with("123", '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 - s._handle_eio_connect('456', 'environ') + s._handle_eio_connect("456", "environ") assert s.manager.initialize.call_count == 1 def test_handle_connect_with_auth(self, eio): s = server.Server() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock() - s.on('connect', handler) - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0{"token":"abc"}') - assert s.manager.is_connected('1', '/') - handler.assert_called_with('1', 'environ', {'token': 'abc'}) - s.eio.send.assert_called_once_with('123', '0{"sid":"1"}') + s.on("connect", handler) + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", '0{"token":"abc"}') + assert s.manager.is_connected("1", "/") + handler.assert_called_with("1", "environ", {"token": "abc"}) + s.eio.send.assert_called_once_with("123", '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 - s._handle_eio_connect('456', 'environ') + s._handle_eio_connect("456", "environ") assert s.manager.initialize.call_count == 1 def test_handle_connect_with_auth_none(self, eio): s = server.Server() s.manager.initialize = mock.MagicMock() handler = mock.MagicMock(side_effect=[TypeError, None]) - s.on('connect', handler) - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - assert s.manager.is_connected('1', '/') - handler.assert_called_with('1', 'environ', None) - s.eio.send.assert_called_once_with('123', '0{"sid":"1"}') + s.on("connect", handler) + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + assert s.manager.is_connected("1", "/") + handler.assert_called_with("1", "environ", None) + s.eio.send.assert_called_once_with("123", '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 - s._handle_eio_connect('456', 'environ') + s._handle_eio_connect("456", "environ") assert s.manager.initialize.call_count == 1 def test_handle_connect_with_default_implied_namespaces(self, eio): s = server.Server() - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - s._handle_eio_message('123', '0/foo,') - assert s.manager.is_connected('1', '/') - assert not s.manager.is_connected('2', '/foo') + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + 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): - s = server.Server(namespaces=['/foo']) - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - s._handle_eio_message('123', '0/foo,') - assert not s.manager.is_connected('1', '/') - assert s.manager.is_connected('1', '/foo') + s = server.Server(namespaces=["/foo"]) + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + 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): - s = server.Server(namespaces='*') - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - s._handle_eio_message('123', '0/foo,') - assert s.manager.is_connected('1', '/') - assert s.manager.is_connected('2', '/foo') + s = server.Server(namespaces="*") + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + 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): s = server.Server() handler = mock.MagicMock() - s.on('connect', handler, namespace='/foo') - s._handle_eio_connect('123', 'environ') - 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_called_once_with('123', '0/foo,{"sid":"1"}') + s.on("connect", handler, namespace="/foo") + s._handle_eio_connect("123", "environ") + 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_called_once_with("123", '0/foo,{"sid":"1"}') def test_handle_connect_namespace_twice(self, eio): s = server.Server() handler = mock.MagicMock() - s.on('connect', handler, namespace='/foo') - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0/foo,') - 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_any_call('123', '0/foo,{"sid":"1"}') - s.eio.send.assert_any_call('123', '4/foo,"Unable to connect"') + s.on("connect", handler, namespace="/foo") + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0/foo,") + 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_any_call("123", '0/foo,{"sid":"1"}') + s.eio.send.assert_any_call("123", '4/foo,"Unable to connect"') def test_handle_connect_always_connect(self, eio): s = server.Server(always_connect=True) s.manager.initialize = mock.MagicMock() handler = mock.MagicMock() - s.on('connect', handler) - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - assert s.manager.is_connected('1', '/') - handler.assert_called_once_with('1', 'environ') - s.eio.send.assert_called_once_with('123', '0{"sid":"1"}') + s.on("connect", handler) + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + assert s.manager.is_connected("1", "/") + handler.assert_called_once_with("1", "environ") + s.eio.send.assert_called_once_with("123", '0{"sid":"1"}') assert s.manager.initialize.call_count == 1 - s._handle_eio_connect('456', 'environ') + s._handle_eio_connect("456", "environ") assert s.manager.initialize.call_count == 1 def test_handle_connect_rejected(self, eio): s = server.Server() handler = mock.MagicMock(return_value=False) - 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') - assert not s.manager.is_connected('1', '/') + 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") + assert not s.manager.is_connected("1", "/") s.eio.send.assert_called_once_with( - '123', '4{"message":"Connection rejected by server"}') - assert s.environ == {'123': 'environ'} + "123", '4{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} def test_handle_connect_namespace_rejected(self, eio): s = server.Server() handler = mock.MagicMock(return_value=False) - s.on('connect', handler, namespace='/foo') - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0/foo,') - assert not s.manager.is_connected('1', '/foo') - handler.assert_called_once_with('1', 'environ') - assert not s.manager.is_connected('1', '/foo') + s.on("connect", handler, namespace="/foo") + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0/foo,") + assert not s.manager.is_connected("1", "/foo") + handler.assert_called_once_with("1", "environ") + assert not s.manager.is_connected("1", "/foo") s.eio.send.assert_called_once_with( - '123', '4/foo,{"message":"Connection rejected by server"}') - assert s.environ == {'123': 'environ'} + "123", '4/foo,{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} def test_handle_connect_rejected_always_connect(self, eio): s = server.Server(always_connect=True) handler = mock.MagicMock(return_value=False) - 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_any_call('123', '0{"sid":"1"}') + 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_any_call("123", '0{"sid":"1"}') s.eio.send.assert_any_call( - '123', '1{"message":"Connection rejected by server"}') - assert s.environ == {'123': 'environ'} + "123", '1{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} def test_handle_connect_namespace_rejected_always_connect(self, eio): s = server.Server(always_connect=True) handler = mock.MagicMock(return_value=False) - s.on('connect', handler, namespace='/foo') - s._handle_eio_connect('123', 'environ') - 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_call('123', '0/foo,{"sid":"1"}') + s.on("connect", handler, namespace="/foo") + s._handle_eio_connect("123", "environ") + 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_call("123", '0/foo,{"sid":"1"}') s.eio.send.assert_any_call( - '123', '1/foo,{"message":"Connection rejected by server"}') - assert s.environ == {'123': 'environ'} + "123", '1/foo,{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} def test_handle_connect_rejected_with_exception(self, eio): s = server.Server() handler = mock.MagicMock( - side_effect=exceptions.ConnectionRefusedError('fail_reason') + side_effect=exceptions.ConnectionRefusedError("fail_reason") ) - 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":"fail_reason"}') - assert s.environ == {'123': 'environ'} + 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":"fail_reason"}') + assert s.environ == {"123": "environ"} def test_handle_connect_rejected_with_empty_exception(self, eio): s = server.Server() - handler = mock.MagicMock( - side_effect=exceptions.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') + handler = mock.MagicMock(side_effect=exceptions.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 rejected by server"}') - assert s.environ == {'123': 'environ'} + "123", '4{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} def test_handle_connect_namespace_rejected_with_exception(self, eio): s = server.Server() handler = mock.MagicMock( - side_effect=exceptions.ConnectionRefusedError('fail_reason', 1) + side_effect=exceptions.ConnectionRefusedError("fail_reason", 1) ) - s.on('connect', handler, namespace='/foo') - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0/foo,') - assert not s.manager.is_connected('1', '/foo') + s.on("connect", handler, namespace="/foo") + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0/foo,") + assert not s.manager.is_connected("1", "/foo") s.eio.send.assert_called_once_with( - '123', '4/foo,{"message":"fail_reason","data":1}' + "123", '4/foo,{"message":"fail_reason","data":1}' ) - assert s.environ == {'123': 'environ'} + assert s.environ == {"123": "environ"} def test_handle_connect_namespace_rejected_with_empty_exception(self, eio): s = server.Server() - handler = mock.MagicMock( - side_effect=exceptions.ConnectionRefusedError() - ) - s.on('connect', handler, namespace='/foo') - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0/foo,') - assert not s.manager.is_connected('1', '/foo') + handler = mock.MagicMock(side_effect=exceptions.ConnectionRefusedError()) + s.on("connect", handler, namespace="/foo") + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0/foo,") + assert not s.manager.is_connected("1", "/foo") s.eio.send.assert_called_once_with( - '123', '4/foo,{"message":"Connection rejected by server"}') - assert s.environ == {'123': 'environ'} + "123", '4/foo,{"message":"Connection rejected by server"}' + ) + assert s.environ == {"123": "environ"} def test_handle_disconnect(self, eio): s = server.Server() s.manager.disconnect = mock.MagicMock() handler = mock.MagicMock() - 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_once_with('1', 'foo') - s.manager.disconnect.assert_called_once_with('1', '/', - ignore_queue=True) + 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_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) + 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 == {} def test_handle_disconnect_namespace(self, eio): s = server.Server() handler = mock.MagicMock() - s.on('disconnect', handler) + s.on("disconnect", handler) handler_namespace = mock.MagicMock() - 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', 'foo') + 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", "foo") handler.assert_not_called() - handler_namespace.assert_called_once_with('1', 'foo') + handler_namespace.assert_called_once_with("1", "foo") assert s.environ == {} def test_handle_disconnect_only_namespace(self, eio): s = server.Server() handler = mock.MagicMock() - s.on('disconnect', handler) + s.on("disconnect", handler) handler_namespace = mock.MagicMock() - s.on('disconnect', handler_namespace, namespace='/foo') - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0/foo,') - s._handle_eio_message('123', '1/foo,') + s.on("disconnect", handler_namespace, namespace="/foo") + s._handle_eio_connect("123", "environ") + 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', s.reason.CLIENT_DISCONNECT) - assert s.environ == {'123': 'environ'} + 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', 'foo') + s._handle_eio_disconnect("123", "foo") def test_handle_event(self, eio): s = server.Server(async_handlers=False) - s.manager.connect('123', '/') + s.manager.connect("123", "/") handler = mock.MagicMock() catchall_handler = mock.MagicMock() - s.on('msg', handler) - s.on('*', catchall_handler) - s._handle_eio_message('123', '2["msg","a","b"]') - s._handle_eio_message('123', '2["my message","a","b","c"]') - handler.assert_called_once_with('1', 'a', 'b') - catchall_handler.assert_called_once_with( - 'my message', '1', 'a', 'b', 'c') + s.on("msg", handler) + s.on("*", catchall_handler) + s._handle_eio_message("123", '2["msg","a","b"]') + s._handle_eio_message("123", '2["my message","a","b","c"]') + handler.assert_called_once_with("1", "a", "b") + catchall_handler.assert_called_once_with("my message", "1", "a", "b", "c") def test_handle_event_with_namespace(self, eio): s = server.Server(async_handlers=False) - s.manager.connect('123', '/foo') + s.manager.connect("123", "/foo") handler = mock.MagicMock() catchall_handler = mock.MagicMock() - s.on('msg', handler, namespace='/foo') - s.on('*', catchall_handler, namespace='/foo') - s._handle_eio_message('123', '2/foo,["msg","a","b"]') - s._handle_eio_message('123', '2/foo,["my message","a","b","c"]') - handler.assert_called_once_with('1', 'a', 'b') - catchall_handler.assert_called_once_with( - 'my message', '1', 'a', 'b', 'c') + s.on("msg", handler, namespace="/foo") + s.on("*", catchall_handler, namespace="/foo") + s._handle_eio_message("123", '2/foo,["msg","a","b"]') + s._handle_eio_message("123", '2/foo,["my message","a","b","c"]') + handler.assert_called_once_with("1", "a", "b") + catchall_handler.assert_called_once_with("my message", "1", "a", "b", "c") def test_handle_event_with_catchall_namespace(self, eio): s = server.Server(async_handlers=False) - sid_foo = s.manager.connect('123', '/foo') - sid_bar = s.manager.connect('123', '/bar') - sid_baz = s.manager.connect('123', '/baz') + sid_foo = s.manager.connect("123", "/foo") + sid_bar = s.manager.connect("123", "/bar") + sid_baz = s.manager.connect("123", "/baz") connect_star_handler = mock.MagicMock() msg_foo_handler = mock.MagicMock() msg_star_handler = mock.MagicMock() star_foo_handler = mock.MagicMock() star_star_handler = mock.MagicMock() my_message_baz_handler = mock.MagicMock() - s.on('connect', connect_star_handler, namespace='*') - s.on('msg', msg_foo_handler, namespace='/foo') - s.on('msg', msg_star_handler, namespace='*') - s.on('*', star_foo_handler, namespace='/foo') - s.on('*', star_star_handler, namespace='*') - s.on('my message', my_message_baz_handler, namespace='/baz') - - s._trigger_event('connect', '/bar', sid_bar) - s._handle_eio_message('123', '2/foo,["msg","a","b"]') - 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.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') - star_foo_handler.assert_called_once_with( - 'my message', sid_foo, 'a', 'b', 'c') + s.on("connect", connect_star_handler, namespace="*") + s.on("msg", msg_foo_handler, namespace="/foo") + s.on("msg", msg_star_handler, namespace="*") + s.on("*", star_foo_handler, namespace="/foo") + s.on("*", star_star_handler, namespace="*") + s.on("my message", my_message_baz_handler, namespace="/baz") + + s._trigger_event("connect", "/bar", sid_bar) + s._handle_eio_message("123", '2/foo,["msg","a","b"]') + 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.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") + star_foo_handler.assert_called_once_with("my message", sid_foo, "a", "b", "c") star_star_handler.assert_called_once_with( - 'my message', '/bar', sid_bar, 'a', 'b', 'c') + "my message", "/bar", sid_bar, "a", "b", "c" + ) - s._handle_eio_message('123', '2/baz,["my message","a","b","c"]') - s._handle_eio_message('123', '2/baz,["msg","a","b"]') - my_message_baz_handler.assert_called_once_with(sid_baz, 'a', 'b', 'c') - msg_star_handler.assert_called_with('/baz', sid_baz, 'a', 'b') + s._handle_eio_message("123", '2/baz,["my message","a","b","c"]') + s._handle_eio_message("123", '2/baz,["msg","a","b"]') + my_message_baz_handler.assert_called_once_with(sid_baz, "a", "b", "c") + msg_star_handler.assert_called_with("/baz", sid_baz, "a", "b") def test_handle_event_with_disconnected_namespace(self, eio): s = server.Server(async_handlers=False) - s.manager.connect('123', '/foo') + s.manager.connect("123", "/foo") handler = mock.MagicMock() - s.on('my message', handler, namespace='/bar') - s._handle_eio_message('123', '2/bar,["my message","a","b","c"]') + s.on("my message", handler, namespace="/bar") + s._handle_eio_message("123", '2/bar,["my message","a","b","c"]') handler.assert_not_called() def test_handle_event_binary(self, eio): s = server.Server(async_handlers=False) - s.manager.connect('123', '/') + s.manager.connect("123", "/") handler = mock.MagicMock() - s.on('my message', handler) + s.on("my message", handler) s._handle_eio_message( - '123', + "123", '52-["my message","a",' '{"_placeholder":true,"num":1},' '{"_placeholder":true,"num":0}]', ) - s._handle_eio_message('123', b'foo') - s._handle_eio_message('123', b'bar') - handler.assert_called_once_with('1', 'a', b'bar', b'foo') + s._handle_eio_message("123", b"foo") + s._handle_eio_message("123", b"bar") + handler.assert_called_once_with("1", "a", b"bar", b"foo") def test_handle_event_binary_ack(self, eio): s = server.Server() s.manager.trigger_callback = mock.MagicMock() - sid = s.manager.connect('123', '/') + sid = s.manager.connect("123", "/") s._handle_eio_message( - '123', '61-321["my message","a",' '{"_placeholder":true,"num":0}]' + "123", '61-321["my message","a",' '{"_placeholder":true,"num":0}]' ) - s._handle_eio_message('123', b'foo') + s._handle_eio_message("123", b"foo") s.manager.trigger_callback.assert_called_once_with( - sid, 321, ['my message', 'a', b'foo'] + sid, 321, ["my message", "a", b"foo"] ) def test_handle_event_with_ack(self, eio): s = server.Server(async_handlers=False) - sid = s.manager.connect('123', '/') - handler = mock.MagicMock(return_value='foo') - s.on('my message', handler) - s._handle_eio_message('123', '21000["my message","foo"]') - handler.assert_called_once_with(sid, 'foo') - s.eio.send.assert_called_once_with('123', '31000["foo"]') + sid = s.manager.connect("123", "/") + handler = mock.MagicMock(return_value="foo") + s.on("my message", handler) + s._handle_eio_message("123", '21000["my message","foo"]') + handler.assert_called_once_with(sid, "foo") + s.eio.send.assert_called_once_with("123", '31000["foo"]') def test_handle_unknown_event_with_ack(self, eio): s = server.Server(async_handlers=False) - s.manager.connect('123', '/') - handler = mock.MagicMock(return_value='foo') - s.on('my message', handler) - s._handle_eio_message('123', '21000["another message","foo"]') + s.manager.connect("123", "/") + handler = mock.MagicMock(return_value="foo") + s.on("my message", handler) + s._handle_eio_message("123", '21000["another message","foo"]') s.eio.send.assert_not_called() def test_handle_event_with_ack_none(self, eio): s = server.Server(async_handlers=False) - sid = s.manager.connect('123', '/') + sid = s.manager.connect("123", "/") handler = mock.MagicMock(return_value=None) - s.on('my message', handler) - s._handle_eio_message('123', '21000["my message","foo"]') - handler.assert_called_once_with(sid, 'foo') - s.eio.send.assert_called_once_with('123', '31000[]') + s.on("my message", handler) + s._handle_eio_message("123", '21000["my message","foo"]') + handler.assert_called_once_with(sid, "foo") + s.eio.send.assert_called_once_with("123", "31000[]") def test_handle_event_with_ack_tuple(self, eio): s = server.Server(async_handlers=False) - handler = mock.MagicMock(return_value=(1, '2', True)) - s.on('my message', handler) - sid = s.manager.connect('123', '/') - s._handle_eio_message('123', '21000["my message","a","b","c"]') - handler.assert_called_once_with(sid, 'a', 'b', 'c') - s.eio.send.assert_called_with( - '123', '31000[1,"2",true]' - ) + handler = mock.MagicMock(return_value=(1, "2", True)) + s.on("my message", handler) + sid = s.manager.connect("123", "/") + s._handle_eio_message("123", '21000["my message","a","b","c"]') + handler.assert_called_once_with(sid, "a", "b", "c") + s.eio.send.assert_called_with("123", '31000[1,"2",true]') def test_handle_event_with_ack_list(self, eio): s = server.Server(async_handlers=False) - handler = mock.MagicMock(return_value=[1, '2', True]) - s.on('my message', handler) - sid = s.manager.connect('123', '/') - s._handle_eio_message('123', '21000["my message","a","b","c"]') - handler.assert_called_once_with(sid, 'a', 'b', 'c') - s.eio.send.assert_called_with( - '123', '31000[[1,"2",true]]' - ) + handler = mock.MagicMock(return_value=[1, "2", True]) + s.on("my message", handler) + sid = s.manager.connect("123", "/") + s._handle_eio_message("123", '21000["my message","a","b","c"]') + handler.assert_called_once_with(sid, "a", "b", "c") + s.eio.send.assert_called_with("123", '31000[[1,"2",true]]') def test_handle_event_with_ack_binary(self, eio): s = server.Server(async_handlers=False) - handler = mock.MagicMock(return_value=b'foo') - s.on('my message', handler) - sid = s.manager.connect('123', '/') - s._handle_eio_message('123', '21000["my message","foo"]') - handler.assert_any_call(sid, 'foo') + handler = mock.MagicMock(return_value=b"foo") + s.on("my message", handler) + sid = s.manager.connect("123", "/") + s._handle_eio_message("123", '21000["my message","foo"]') + handler.assert_any_call(sid, "foo") def test_handle_error_packet(self, eio): s = server.Server() with pytest.raises(ValueError): - s._handle_eio_message('123', '4') + s._handle_eio_message("123", "4") def test_handle_invalid_packet(self, eio): s = server.Server() with pytest.raises(ValueError): - s._handle_eio_message('123', '9') + s._handle_eio_message("123", "9") def test_send_with_ack(self, eio): s = server.Server() - s.handlers['/'] = {} - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') + s.handlers["/"] = {} + s._handle_eio_connect("123", "environ") + 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) - s._send_packet('123', packet.Packet( - packet.EVENT, ['my event', 'foo'], id=id1)) - s._send_packet('123', packet.Packet( - packet.EVENT, ['my event', 'bar'], id=id2)) - s._handle_eio_message('123', '31["foo",2]') - cb.assert_called_once_with('foo', 2) + id1 = s.manager._generate_ack_id("1", cb) + id2 = s.manager._generate_ack_id("1", cb) + s._send_packet("123", packet.Packet(packet.EVENT, ["my event", "foo"], id=id1)) + s._send_packet("123", packet.Packet(packet.EVENT, ["my event", "bar"], id=id2)) + s._handle_eio_message("123", '31["foo",2]') + cb.assert_called_once_with("foo", 2) def test_send_with_ack_namespace(self, eio): s = server.Server() - s.handlers['/foo'] = {} - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0/foo,') + s.handlers["/foo"] = {} + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0/foo,") cb = mock.MagicMock() - id = s.manager._generate_ack_id('1', cb) - s._send_packet('123', packet.Packet( - packet.EVENT, ['my event', 'foo'], namespace='/foo', id=id)) - s._handle_eio_message('123', '3/foo,1["foo",2]') - cb.assert_called_once_with('foo', 2) + id = s.manager._generate_ack_id("1", cb) + s._send_packet( + "123", + packet.Packet(packet.EVENT, ["my event", "foo"], namespace="/foo", id=id), + ) + s._handle_eio_message("123", '3/foo,1["foo",2]') + cb.assert_called_once_with("foo", 2) def test_session(self, eio): fake_session = {} def fake_get_session(eio_sid): - assert eio_sid == '123' + assert eio_sid == "123" return fake_session def fake_save_session(eio_sid, session): global fake_session - assert eio_sid == '123' + assert eio_sid == "123" fake_session = session s = server.Server() - s.handlers['/'] = {} - s.handlers['/ns'] = {} + s.handlers["/"] = {} + s.handlers["/ns"] = {} s.eio.get_session = fake_get_session s.eio.save_session = fake_save_session - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - s._handle_eio_message('123', '0/ns') - sid = s.manager.sid_from_eio_sid('123', '/') - sid2 = s.manager.sid_from_eio_sid('123', '/ns') - s.save_session(sid, {'foo': 'bar'}) + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + s._handle_eio_message("123", "0/ns") + sid = s.manager.sid_from_eio_sid("123", "/") + sid2 = s.manager.sid_from_eio_sid("123", "/ns") + s.save_session(sid, {"foo": "bar"}) with s.session(sid) as session: - assert session == {'foo': 'bar'} - session['foo'] = 'baz' - session['bar'] = 'foo' - assert s.get_session(sid) == {'foo': 'baz', 'bar': 'foo'} - assert fake_session == {'/': {'foo': 'baz', 'bar': 'foo'}} - with s.session(sid2, namespace='/ns') as session: + assert session == {"foo": "bar"} + session["foo"] = "baz" + session["bar"] = "foo" + assert s.get_session(sid) == {"foo": "baz", "bar": "foo"} + assert fake_session == {"/": {"foo": "baz", "bar": "foo"}} + with s.session(sid2, namespace="/ns") as session: assert session == {} - session['a'] = 'b' - assert s.get_session(sid2, namespace='/ns') == {'a': 'b'} + session["a"] = "b" + assert s.get_session(sid2, namespace="/ns") == {"a": "b"} assert fake_session == { - '/': {'foo': 'baz', 'bar': 'foo'}, - '/ns': {'a': 'b'}, + "/": {"foo": "baz", "bar": "foo"}, + "/ns": {"a": "b"}, } def test_disconnect(self, eio): s = server.Server() - s.handlers['/'] = {} - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - s.disconnect('1') - s.eio.send.assert_any_call('123', '1') + s.handlers["/"] = {} + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + s.disconnect("1") + s.eio.send.assert_any_call("123", "1") def test_disconnect_ignore_queue(self, eio): s = server.Server() - s.handlers['/'] = {} - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - s.disconnect('1', ignore_queue=True) - s.eio.send.assert_any_call('123', '1') + s.handlers["/"] = {} + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + s.disconnect("1", ignore_queue=True) + s.eio.send.assert_any_call("123", "1") def test_disconnect_namespace(self, eio): s = server.Server() - s.handlers['/foo'] = {} - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0/foo,') - s.disconnect('1', namespace='/foo') - s.eio.send.assert_any_call('123', '1/foo,') + s.handlers["/foo"] = {} + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0/foo,") + s.disconnect("1", namespace="/foo") + s.eio.send.assert_any_call("123", "1/foo,") def test_disconnect_twice(self, eio): s = server.Server() - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - s.disconnect('1') + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + s.disconnect("1") calls = s.eio.send.call_count - s.disconnect('1') + s.disconnect("1") assert calls == s.eio.send.call_count def test_disconnect_twice_namespace(self, eio): s = server.Server() - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0/foo,') - s.disconnect('123', namespace='/foo') + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0/foo,") + s.disconnect("123", namespace="/foo") calls = s.eio.send.call_count - s.disconnect('123', namespace='/foo') + s.disconnect("123", namespace="/foo") assert calls == s.eio.send.call_count def test_namespace_handler(self, eio): @@ -838,67 +821,66 @@ class TestServer: class MyNamespace(namespace.Namespace): def on_connect(self, sid, environ): - result['result'] = (sid, environ) + result["result"] = (sid, environ) def on_disconnect(self, sid, reason): - result['result'] = ('disconnect', sid, reason) + result["result"] = ("disconnect", sid, reason) def on_foo(self, sid, data): - result['result'] = (sid, data) + result["result"] = (sid, data) def on_bar(self, sid): - result['result'] = 'bar' + result["result"] = "bar" def on_baz(self, sid, data1, data2): - result['result'] = (data1, data2) + result["result"] = (data1, data2) s = server.Server(async_handlers=False) - s.register_namespace(MyNamespace('/foo')) - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0/foo,') - assert result['result'] == ('1', 'environ') - s._handle_eio_message('123', '2/foo,["foo","a"]') - assert result['result'] == ('1', 'a') - s._handle_eio_message('123', '2/foo,["bar"]') - assert result['result'] == 'bar' - s._handle_eio_message('123', '2/foo,["baz","a","b"]') - assert result['result'] == ('a', 'b') - s.disconnect('1', '/foo') - assert result['result'] == ('disconnect', '1', - s.reason.SERVER_DISCONNECT) + s.register_namespace(MyNamespace("/foo")) + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0/foo,") + assert result["result"] == ("1", "environ") + s._handle_eio_message("123", '2/foo,["foo","a"]') + assert result["result"] == ("1", "a") + s._handle_eio_message("123", '2/foo,["bar"]') + assert result["result"] == "bar" + s._handle_eio_message("123", '2/foo,["baz","a","b"]') + assert result["result"] == ("a", "b") + s.disconnect("1", "/foo") + assert result["result"] == ("disconnect", "1", s.reason.SERVER_DISCONNECT) def test_catchall_namespace_handler(self, eio): result = {} class MyNamespace(namespace.Namespace): def on_connect(self, ns, sid, environ): - result['result'] = (sid, ns, environ) + result["result"] = (sid, ns, environ) def on_disconnect(self, ns, sid): - result['result'] = ('disconnect', sid, ns) + result["result"] = ("disconnect", sid, ns) def on_foo(self, ns, sid, data): - result['result'] = (sid, ns, data) + result["result"] = (sid, ns, data) def on_bar(self, ns, sid): - result['result'] = 'bar' + ns + result["result"] = "bar" + ns def on_baz(self, ns, sid, data1, data2): - result['result'] = (ns, data1, data2) - - s = server.Server(async_handlers=False, namespaces='*') - s.register_namespace(MyNamespace('*')) - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0/foo,') - assert result['result'] == ('1', '/foo', 'environ') - s._handle_eio_message('123', '2/foo,["foo","a"]') - assert result['result'] == ('1', '/foo', 'a') - s._handle_eio_message('123', '2/foo,["bar"]') - assert result['result'] == 'bar/foo' - s._handle_eio_message('123', '2/foo,["baz","a","b"]') - assert result['result'] == ('/foo', 'a', 'b') - s.disconnect('1', '/foo') - assert result['result'] == ('disconnect', '1', '/foo') + result["result"] = (ns, data1, data2) + + s = server.Server(async_handlers=False, namespaces="*") + s.register_namespace(MyNamespace("*")) + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0/foo,") + assert result["result"] == ("1", "/foo", "environ") + s._handle_eio_message("123", '2/foo,["foo","a"]') + assert result["result"] == ("1", "/foo", "a") + s._handle_eio_message("123", '2/foo,["bar"]') + assert result["result"] == "bar/foo" + s._handle_eio_message("123", '2/foo,["baz","a","b"]') + assert result["result"] == ("/foo", "a", "b") + s.disconnect("1", "/foo") + assert result["result"] == ("disconnect", "1", "/foo") def test_bad_namespace_handler(self, eio): class Dummy: @@ -922,12 +904,12 @@ class TestServer: def test_get_environ(self, eio): s = server.Server() - s.handlers['/'] = {} - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') - sid = s.manager.sid_from_eio_sid('123', '/') - assert s.get_environ(sid) == 'environ' - assert s.get_environ('foo') is None + s.handlers["/"] = {} + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") + sid = s.manager.sid_from_eio_sid("123", "/") + assert s.get_environ(sid) == "environ" + assert s.get_environ("foo") is None def test_logger(self, eio): s = server.Server(logger=False) @@ -939,17 +921,15 @@ class TestServer: s = server.Server(logger=True) assert s.logger.getEffectiveLevel() == logging.WARNING s.logger.setLevel(logging.NOTSET) - s = server.Server(logger='foo') - assert s.logger == 'foo' + s = server.Server(logger="foo") + assert s.logger == "foo" def test_engineio_logger(self, eio): - server.Server(engineio_logger='foo') - eio.assert_called_once_with( - **{'logger': 'foo', 'async_handlers': False} - ) + server.Server(engineio_logger="foo") + eio.assert_called_once_with(**{"logger": "foo", "async_handlers": False}) def test_msgpack(self, eio): - s = server.Server(serializer='msgpack') + s = server.Server(serializer="msgpack") assert s.packet_class == msgpack_packet.MsgPackPacket def test_custom_serializer(self, eio): @@ -966,39 +946,37 @@ class TestServer: class CustomJSON: @staticmethod def dumps(*args, **kwargs): - return '*** encoded ***' + return "*** encoded ***" @staticmethod def loads(*args, **kwargs): - return '+++ decoded +++' + return "+++ decoded +++" server.Server(json=CustomJSON) - eio.assert_called_once_with( - **{'json': CustomJSON, 'async_handlers': False} - ) + eio.assert_called_once_with(**{"json": CustomJSON, "async_handlers": False}) pkt = packet.Packet( packet_type=packet.EVENT, - data={'foo': 'bar'}, + data={"foo": "bar"}, ) - assert pkt.encode() == '2*** encoded ***' + assert pkt.encode() == "2*** encoded ***" pkt2 = packet.Packet(encoded_packet=pkt.encode()) - assert pkt2.data == '+++ decoded +++' + assert pkt2.data == "+++ decoded +++" # restore the default JSON module packet.Packet.json = json def test_async_handlers(self, eio): s = server.Server(async_handlers=True) - sid = s.manager.connect('123', '/') - s._handle_eio_message('123', '2["my message","a","b","c"]') + sid = s.manager.connect("123", "/") + 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, sid, - '123', - ['my message', 'a', 'b', 'c'], - '/', + "123", + ["my message", "a", "b", "c"], + "/", None, ) @@ -1009,10 +987,8 @@ class TestServer: def test_start_background_task(self, eio): s = server.Server() - s.start_background_task('foo', 'bar', baz='baz') - s.eio.start_background_task.assert_called_once_with( - 'foo', 'bar', baz='baz' - ) + 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): s = server.Server() diff --git a/tests/common/test_simple_client.py b/tests/common/test_simple_client.py index b17afbc..5fdbc71 100644 --- a/tests/common/test_simple_client.py +++ b/tests/common/test_simple_client.py @@ -1,14 +1,16 @@ from unittest import mock + import pytest + from socketio import SimpleClient -from socketio.exceptions import SocketIOError, TimeoutError, DisconnectedError +from socketio.exceptions import DisconnectedError, SocketIOError, TimeoutError class TestSimpleClient: def test_constructor(self): - client = SimpleClient(1, '2', a='3', b=4) - assert client.client_args == (1, '2') - assert client.client_kwargs == {'a': '3', 'b': 4} + client = SimpleClient(1, "2", a="3", b=4) + assert client.client_args == (1, "2") + assert client.client_kwargs == {"a": "3", "b": 4} assert client.client is None assert client.input_buffer == [] assert not client.connected @@ -18,17 +20,30 @@ class TestSimpleClient: original_client_class = SimpleClient.client_class SimpleClient.client_class = mock_client - client = SimpleClient(123, a='b') - 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') + client = SimpleClient(123, a="b") + 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') + "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' + 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 @@ -38,57 +53,68 @@ class TestSimpleClient: 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') - mock_client.assert_called_once_with(123, a='b') + with SimpleClient(123, a="b") as 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') + "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' + 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') + client = SimpleClient(123, a="b") client.client = mock.MagicMock() client.connected = True with pytest.raises(RuntimeError): - client.connect('url') + client.connect("url") def test_properties(self): client = SimpleClient() - client.client = mock.MagicMock(transport='websocket') - client.client.get_sid.return_value = 'sid' + client.client = mock.MagicMock(transport="websocket") + client.client.get_sid.return_value = "sid" client.connected_event.set() client.connected = True - assert client.sid == 'sid' - assert client.transport == 'websocket' + assert client.sid == "sid" + assert client.transport == "websocket" def test_emit(self): client = SimpleClient() client.client = mock.MagicMock() - client.namespace = '/ns' + client.namespace = "/ns" client.connected_event.set() client.connected = True - client.emit('foo', 'bar') - client.client.emit.assert_called_once_with('foo', 'bar', - namespace='/ns') + client.emit("foo", "bar") + client.client.emit.assert_called_once_with("foo", "bar", namespace="/ns") def test_emit_disconnected(self): client = SimpleClient() client.connected_event.set() client.connected = False with pytest.raises(DisconnectedError): - client.emit('foo', 'bar') + client.emit("foo", "bar") def test_emit_retries(self): client = SimpleClient() @@ -97,44 +123,44 @@ class TestSimpleClient: client.client = mock.MagicMock() client.client.emit.side_effect = [SocketIOError(), None] - client.emit('foo', 'bar') - client.client.emit.assert_called_with('foo', 'bar', namespace='/') + client.emit("foo", "bar") + client.client.emit.assert_called_with("foo", "bar", namespace="/") def test_call(self): client = SimpleClient() client.client = mock.MagicMock() - client.client.call.return_value = 'result' - client.namespace = '/ns' + client.client.call.return_value = "result" + client.namespace = "/ns" client.connected_event.set() client.connected = True - assert client.call('foo', 'bar') == 'result' - client.client.call.assert_called_once_with('foo', 'bar', - namespace='/ns', timeout=60) + assert client.call("foo", "bar") == "result" + client.client.call.assert_called_once_with( + "foo", "bar", namespace="/ns", timeout=60 + ) def test_call_disconnected(self): client = SimpleClient() client.connected_event.set() client.connected = False with pytest.raises(DisconnectedError): - client.call('foo', 'bar') + client.call("foo", "bar") def test_call_retries(self): client = SimpleClient() client.connected_event.set() client.connected = True client.client = mock.MagicMock() - client.client.call.side_effect = [SocketIOError(), 'result'] + client.client.call.side_effect = [SocketIOError(), "result"] - assert client.call('foo', 'bar') == 'result' - client.client.call.assert_called_with('foo', 'bar', namespace='/', - timeout=60) + assert client.call("foo", "bar") == "result" + client.client.call.assert_called_with("foo", "bar", namespace="/", timeout=60) def test_receive_with_input_buffer(self): client = SimpleClient() - client.input_buffer = ['foo', 'bar'] - assert client.receive() == 'foo' - assert client.receive() == 'bar' + client.input_buffer = ["foo", "bar"] + assert client.receive() == "foo" + assert client.receive() == "bar" def test_receive_without_input_buffer(self): client = SimpleClient() @@ -143,11 +169,11 @@ class TestSimpleClient: client.input_event = mock.MagicMock() def fake_wait(timeout=None): - client.input_buffer = ['foo'] + client.input_buffer = ["foo"] return True client.input_event.wait = fake_wait - assert client.receive() == 'foo' + assert client.receive() == "foo" def test_receive_with_timeout(self): client = SimpleClient() diff --git a/tests/performance/binary_packet.py b/tests/performance/binary_packet.py index b9084ee..1fd61ae 100644 --- a/tests/performance/binary_packet.py +++ b/tests/performance/binary_packet.py @@ -1,9 +1,10 @@ import time + from socketio import packet def test(): - p = packet.Packet(packet.EVENT, {'foo': b'bar'}) + p = packet.Packet(packet.EVENT, {"foo": b"bar"}) start = time.time() count = 0 while True: @@ -17,6 +18,6 @@ def test(): return count -if __name__ == '__main__': +if __name__ == "__main__": count = test() - print('binary_packet:', count, 'packets processed.') + print("binary_packet:", count, "packets processed.") diff --git a/tests/performance/json_packet.py b/tests/performance/json_packet.py index 6861a5c..7499a22 100644 --- a/tests/performance/json_packet.py +++ b/tests/performance/json_packet.py @@ -1,9 +1,10 @@ import time + from socketio import packet def test(): - p = packet.Packet(packet.EVENT, {'foo': 'bar'}) + p = packet.Packet(packet.EVENT, {"foo": "bar"}) start = time.time() count = 0 while True: @@ -14,6 +15,6 @@ def test(): return count -if __name__ == '__main__': +if __name__ == "__main__": count = test() - print('json_packet:', count, 'packets processed.') + print("json_packet:", count, "packets processed.") diff --git a/tests/performance/namespace_packet.py b/tests/performance/namespace_packet.py index 4d560a7..927f470 100644 --- a/tests/performance/namespace_packet.py +++ b/tests/performance/namespace_packet.py @@ -1,9 +1,10 @@ import time + from socketio import packet def test(): - p = packet.Packet(packet.EVENT, 'hello', namespace='/foo') + p = packet.Packet(packet.EVENT, "hello", namespace="/foo") start = time.time() count = 0 while True: @@ -14,6 +15,6 @@ def test(): return count -if __name__ == '__main__': +if __name__ == "__main__": count = test() - print('namespace_packet:', count, 'packets processed.') + print("namespace_packet:", count, "packets processed.") diff --git a/tests/performance/server_receive.py b/tests/performance/server_receive.py index 77f6161..13294fd 100644 --- a/tests/performance/server_receive.py +++ b/tests/performance/server_receive.py @@ -1,4 +1,5 @@ import time + import socketio @@ -6,16 +7,16 @@ def test(): s = socketio.Server(async_handlers=False) start = time.time() count = 0 - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") while True: - s._handle_eio_message('123', '2["test","hello"]') + s._handle_eio_message("123", '2["test","hello"]') count += 1 if time.time() - start >= 5: break return count -if __name__ == '__main__': +if __name__ == "__main__": count = test() - print('server_receive:', count, 'packets received.') + print("server_receive:", count, "packets received.") diff --git a/tests/performance/server_send.py b/tests/performance/server_send.py index b6b2d70..030e6fa 100644 --- a/tests/performance/server_send.py +++ b/tests/performance/server_send.py @@ -1,4 +1,5 @@ import time + import socketio @@ -14,16 +15,16 @@ def test(): s = Server() start = time.time() count = 0 - s._handle_eio_connect('123', 'environ') - s._handle_eio_message('123', '0') + s._handle_eio_connect("123", "environ") + s._handle_eio_message("123", "0") while True: - s.emit('test', 'hello') + s.emit("test", "hello") count += 1 if time.time() - start >= 5: break return count -if __name__ == '__main__': +if __name__ == "__main__": count = test() - print('server_send:', count, 'packets received.') + print("server_send:", count, "packets received.") diff --git a/tests/performance/server_send_broadcast.py b/tests/performance/server_send_broadcast.py index ce99d0a..d40677a 100644 --- a/tests/performance/server_send_broadcast.py +++ b/tests/performance/server_send_broadcast.py @@ -1,4 +1,5 @@ import time + import socketio @@ -15,16 +16,16 @@ def test(): start = time.time() count = 0 for i in range(100): - s._handle_eio_connect(str(i), 'environ') - s._handle_eio_message(str(i), '0') + s._handle_eio_connect(str(i), "environ") + s._handle_eio_message(str(i), "0") while True: - s.emit('test', 'hello') + s.emit("test", "hello") count += 1 if time.time() - start >= 5: break return count -if __name__ == '__main__': +if __name__ == "__main__": count = test() - print('server_send:', count, 'packets received.') + print("server_send:", count, "packets received.") diff --git a/tests/performance/text_packet.py b/tests/performance/text_packet.py index a28dfd1..5b68b17 100644 --- a/tests/performance/text_packet.py +++ b/tests/performance/text_packet.py @@ -1,9 +1,10 @@ import time + from socketio import packet def test(): - p = packet.Packet(packet.EVENT, 'hello') + p = packet.Packet(packet.EVENT, "hello") start = time.time() count = 0 while True: @@ -14,6 +15,6 @@ def test(): return count -if __name__ == '__main__': +if __name__ == "__main__": count = test() - print('text_packet:', count, 'packets processed.') + print("text_packet:", count, "packets processed.") diff --git a/tests/web_server.py b/tests/web_server.py index cb24668..16b9361 100644 --- a/tests/web_server.py +++ b/tests/web_server.py @@ -1,8 +1,10 @@ import threading import time from socketserver import ThreadingMixIn -from wsgiref.simple_server import make_server, WSGIServer, WSGIRequestHandler +from wsgiref.simple_server import WSGIRequestHandler, WSGIServer, make_server + import requests + import socketio @@ -15,13 +17,14 @@ class SocketIOWebServer: Note 2: This class only supports the "threading" async_mode, with WebSocket support provided by the simple-websocket package. """ + def __init__(self, sio): - if sio.async_mode != 'threading': + if sio.async_mode != "threading": raise ValueError('The async_mode must be "threading"') def http_app(environ, start_response): - start_response('200 OK', [('Content-Type', 'text/plain')]) - return [b'OK'] + start_response("200 OK", [("Content-Type", "text/plain")]) + return [b"OK"] self.sio = sio self.app = socketio.WSGIApp(sio, http_app) @@ -35,6 +38,7 @@ class SocketIOWebServer: The server is started in a background thread. """ + class ThreadingWSGIServer(ThreadingMixIn, WSGIServer): pass @@ -44,20 +48,21 @@ class SocketIOWebServer: # pass the raw socket to the WSGI app so that it can be used # by WebSocket connections (hack copied from gunicorn) - env['gunicorn.socket'] = self.connection + env["gunicorn.socket"] = self.connection return env - self.httpd = make_server('', port, self._app_wrapper, - ThreadingWSGIServer, WebSocketRequestHandler) + self.httpd = make_server( + "", port, self._app_wrapper, ThreadingWSGIServer, WebSocketRequestHandler + ) self.thread = threading.Thread(target=self.httpd.serve_forever) self.thread.start() # wait for the server to start while True: try: - r = requests.get(f'http://localhost:{port}/') + r = requests.get(f"http://localhost:{port}/") r.raise_for_status() - if r.text == 'OK': + if r.text == "OK": break except: time.sleep(0.1) @@ -77,5 +82,5 @@ class SocketIOWebServer: except StopIteration: # end the WebSocket request without sending a response # (this is a hack that was copied from gunicorn's threaded worker) - start_response('200 OK', []) + start_response("200 OK", []) return []