Browse Source

fix and format by ruff

pull/1284/head
Placeless 2 years ago
parent
commit
03c711b418
  1. 72
      docs/conf.py
  2. 8
      examples/client/async/fiddle_client.py
  3. 10
      examples/client/async/latency_client.py
  4. 8
      examples/client/sync/fiddle_client.py
  5. 10
      examples/client/sync/latency_client.py
  6. 47
      examples/server/aiohttp/app.py
  7. 18
      examples/server/aiohttp/fiddle.py
  8. 14
      examples/server/aiohttp/latency.py
  9. 83
      examples/server/asgi/app.py
  10. 22
      examples/server/asgi/fastapi-fiddle.py
  11. 23
      examples/server/asgi/fiddle.py
  12. 19
      examples/server/asgi/latency.py
  13. 26
      examples/server/asgi/litestar-fiddle.py
  14. 49
      examples/server/sanic/app.py
  15. 16
      examples/server/sanic/fiddle.py
  16. 13
      examples/server/sanic/latency.py
  17. 37
      examples/server/tornado/app.py
  18. 8
      examples/server/tornado/fiddle.py
  19. 4
      examples/server/tornado/latency.py
  20. 85
      examples/server/wsgi/app.py
  21. 2
      examples/server/wsgi/django_socketio/django_socketio/asgi.py
  22. 74
      examples/server/wsgi/django_socketio/django_socketio/settings.py
  23. 4
      examples/server/wsgi/django_socketio/django_socketio/urls.py
  24. 2
      examples/server/wsgi/django_socketio/django_socketio/wsgi.py
  25. 4
      examples/server/wsgi/django_socketio/manage.py
  26. 2
      examples/server/wsgi/django_socketio/socketio_app/admin.py
  27. 4
      examples/server/wsgi/django_socketio/socketio_app/apps.py
  28. 2
      examples/server/wsgi/django_socketio/socketio_app/models.py
  29. 2
      examples/server/wsgi/django_socketio/socketio_app/tests.py
  30. 2
      examples/server/wsgi/django_socketio/socketio_app/urls.py
  31. 35
      examples/server/wsgi/django_socketio/socketio_app/views.py
  32. 42
      examples/server/wsgi/fiddle.py
  33. 38
      examples/server/wsgi/latency.py
  34. 4
      examples/simple-client/async/fiddle_client.py
  35. 10
      examples/simple-client/async/latency_client.py
  36. 4
      examples/simple-client/sync/fiddle_client.py
  37. 10
      examples/simple-client/sync/latency_client.py
  38. 32
      src/socketio/__init__.py
  39. 400
      src/socketio/admin.py
  40. 25
      src/socketio/asgi.py
  41. 399
      src/socketio/async_admin.py
  42. 52
      src/socketio/async_aiopika_manager.py
  43. 186
      src/socketio/async_client.py
  44. 30
      src/socketio/async_manager.py
  45. 125
      src/socketio/async_namespace.py
  46. 210
      src/socketio/async_pubsub_manager.py
  47. 54
      src/socketio/async_redis_manager.py
  48. 306
      src/socketio/async_server.py
  49. 48
      src/socketio/async_simple_client.py
  50. 69
      src/socketio/base_client.py
  51. 18
      src/socketio/base_manager.py
  52. 2
      src/socketio/base_namespace.py
  53. 59
      src/socketio/base_server.py
  54. 178
      src/socketio/client.py
  55. 11
      src/socketio/exceptions.py
  56. 28
      src/socketio/kafka_manager.py
  57. 69
      src/socketio/kombu_manager.py
  58. 20
      src/socketio/manager.py
  59. 18
      src/socketio/middleware.py
  60. 8
      src/socketio/msgpack_packet.py
  61. 120
      src/socketio/namespace.py
  62. 124
      src/socketio/packet.py
  63. 192
      src/socketio/pubsub_manager.py
  64. 63
      src/socketio/redis_manager.py
  65. 273
      src/socketio/server.py
  66. 42
      src/socketio/simple_client.py
  67. 6
      src/socketio/tornado.py
  68. 58
      src/socketio/zmq_manager.py
  69. 263
      tests/async/test_admin.py
  70. 688
      tests/async/test_client.py
  71. 450
      tests/async/test_manager.py
  72. 296
      tests/async/test_namespace.py
  73. 555
      tests/async/test_pubsub_manager.py
  74. 899
      tests/async/test_server.py
  75. 132
      tests/async/test_simple_client.py
  76. 20
      tests/asyncio_web_server.py
  77. 247
      tests/common/test_admin.py
  78. 798
      tests/common/test_client.py
  79. 391
      tests/common/test_manager.py
  80. 18
      tests/common/test_middleware.py
  81. 14
      tests/common/test_msgpack_packet.py
  82. 263
      tests/common/test_namespace.py
  83. 162
      tests/common/test_packet.py
  84. 539
      tests/common/test_pubsub_manager.py
  85. 867
      tests/common/test_server.py
  86. 124
      tests/common/test_simple_client.py
  87. 6
      tests/performance/binary_packet.py
  88. 6
      tests/performance/json_packet.py
  89. 6
      tests/performance/namespace_packet.py
  90. 10
      tests/performance/server_receive.py
  91. 10
      tests/performance/server_send.py
  92. 10
      tests/performance/server_send_broadcast.py
  93. 6
      tests/performance/text_packet.py
  94. 21
      tests/web_server.py

72
docs/conf.py

@ -19,14 +19,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 +39,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 +77,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 +111,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 +120,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 +135,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 +150,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 +160,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 +187,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 -------------------------------------------------

8
examples/client/async/fiddle_client.py

@ -6,12 +6,12 @@ sio = socketio.AsyncClient()
@sio.event
async def connect():
print('connected to server')
print("connected to server")
@sio.event
async def disconnect():
print('disconnected from server')
print("disconnected from server")
@sio.event
@ -20,9 +20,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())

10
examples/client/async/latency_client.py

@ -10,12 +10,12 @@ 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()
@ -23,16 +23,16 @@ async def connect():
async def pong_from_server():
global start_timer
latency = time.time() - start_timer
print('latency is {0:.2f} ms'.format(latency * 1000))
print("latency is {0:.2f} ms".format(latency * 1000))
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())

8
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():
print('disconnected from server')
print("disconnected from server")
@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()

10
examples/client/sync/latency_client.py

@ -8,12 +8,12 @@ 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()
@ -21,12 +21,12 @@ def connect():
def pong_from_server():
global start_timer
latency = time.time() - start_timer
print('latency is {0:.2f} ms'.format(latency * 1000))
print("latency is {0:.2f} ms".format(latency * 1000))
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()

47
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):
print('Client disconnected')
print("Client disconnected")
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())

18
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):
print('disconnected', sid)
print("disconnected", 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)

14
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)

83
examples/server/asgi/app.py

@ -4,25 +4,31 @@
# Admin UI hosted at https://admin.socket.io
instrument = False
admin_login = {
'username': 'admin',
'password': 'python', # change this to a strong secret for production use!
"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,65 +38,66 @@ 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):
print('Client disconnected')
print("Client disconnected")
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)

22
examples/server/asgi/fastapi-fiddle.py

@ -6,32 +6,32 @@ 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)

23
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):
print('disconnected', sid)
print("disconnected", 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)

19
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)

26
examples/server/asgi/litestar-fiddle.py

@ -5,34 +5,36 @@ 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)

49
examples/server/sanic/app.py

@ -3,8 +3,8 @@ from sanic.response import html
import socketio
sio = socketio.AsyncServer(async_mode='sanic')
app = Sanic(name='sanic_application')
sio = socketio.AsyncServer(async_mode="sanic")
app = Sanic(name="sanic_application")
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):
print('Client disconnected')
print("Client disconnected")
app.static('/static', './static')
app.static("/static", "./static")
if __name__ == '__main__':
if __name__ == "__main__":
app.run()

16
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()
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):
print('disconnected', sid)
print("disconnected", sid)
app.static('/static', './static')
app.static("/static", "./static")
if __name__ == '__main__':
if __name__ == "__main__":
app.run()

13
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()
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()

37
examples/server/tornado/app.py

@ -9,7 +9,7 @@ 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):
print('Client disconnected')
print("Client disconnected")
def main():

8
examples/server/tornado/fiddle.py

@ -9,7 +9,7 @@ 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):
print('disconnected', sid)
print("disconnected", sid)
def main():

4
examples/server/tornado/latency.py

@ -9,7 +9,7 @@ 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():

85
examples/server/wsgi/app.py

@ -7,8 +7,8 @@ async_mode = None
# Admin UI hosted at https://admin.socket.io
instrument = False
admin_login = {
'username': 'admin',
'password': 'python', # change this to a strong secret for production use!
"username": "admin",
"password": "python", # change this to a strong secret for production use!
}
from flask import Flask, render_template
@ -16,16 +16,19 @@ 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 +38,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,39 +93,45 @@ 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):
print('Client disconnected')
print("Client disconnected")
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 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)

2
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()

74
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"

4
examples/server/wsgi/django_socketio/django_socketio/urls.py

@ -18,6 +18,6 @@ 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")),
]

2
examples/server/wsgi/django_socketio/django_socketio/wsgi.py

@ -14,7 +14,7 @@ import socketio
from socketio_app.views import sio
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_socketio.settings')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "django_socketio.settings")
django_app = get_wsgi_application()
application = socketio.WSGIApp(sio, django_app)

4
examples/server/wsgi/django_socketio/manage.py

@ -6,7 +6,7 @@ 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 +18,5 @@ def main():
execute_from_command_line(sys.argv)
if __name__ == '__main__':
if __name__ == "__main__":
main()

2
examples/server/wsgi/django_socketio/socketio_app/admin.py

@ -1,3 +1 @@
from django.contrib import admin
# Register your models here.

4
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"

2
examples/server/wsgi/django_socketio/socketio_app/models.py

@ -1,3 +1 @@
from django.db import models
# Create your models here.

2
examples/server/wsgi/django_socketio/socketio_app/tests.py

@ -1,3 +1 @@
from django.test import TestCase
# Create your tests here.

2
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"),
]

35
examples/server/wsgi/django_socketio/socketio_app/views.py

@ -17,7 +17,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 +26,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 +73,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):
print('Client disconnected')
print("Client disconnected")

42
examples/server/wsgi/fiddle.py

@ -11,47 +11,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):
print('disconnected', sid)
print("disconnected", 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)

38
examples/server/wsgi/latency.py

@ -11,41 +11,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)

4
examples/simple-client/async/fiddle_client.py

@ -4,9 +4,9 @@ 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())

10
examples/simple-client/async/latency_client.py

@ -5,17 +5,17 @@ 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('latency is {0:.2f} ms'.format(latency * 1000))
print("latency is {0:.2f} ms".format(latency * 1000))
await asyncio.sleep(1)
if __name__ == '__main__':
if __name__ == "__main__":
asyncio.run(main())

4
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()

10
examples/simple-client/sync/latency_client.py

@ -4,17 +4,17 @@ 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('latency is {0:.2f} ms'.format(latency * 1000))
print("latency is {0:.2f} ms".format(latency * 1000))
time.sleep(1)
if __name__ == '__main__':
if __name__ == "__main__":
main()

32
src/socketio/__init__.py

@ -19,10 +19,28 @@ from .async_redis_manager import AsyncRedisManager
from .async_aiopika_manager import AsyncAioPikaManager
from .asgi import ASGIApp
__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__ = [
"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",
]

400
src/socketio/admin.py

@ -16,15 +16,15 @@ class EventBuffer:
def push(self, type, count=1):
timestamp = int(time.time()) * 1000
key = '{};{}'.format(timestamp, type)
key = "{};{}".format(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 +33,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 <https://socket.io/docs/v4/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 +69,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 = {}
@ -86,13 +96,11 @@ class InstrumentedServer:
self.sio.manager.disconnect = self._disconnect
# 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
@ -104,44 +112,47 @@ class InstrumentedServer:
self.sio._handle_event_internal = self._handle_event_internal
# report engine.io connections
self.sio.eio.on('connect', self._handle_eio_connect)
self.sio.eio.on('disconnect', self._handle_eio_disconnect)
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.manager.connect = self.sio.manager.__connect
self.sio.manager.disconnect = self.sio.manager.__disconnect
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._handle_event_internal = self.sio.__handle_event_internal
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):
@ -154,31 +165,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)
@ -186,18 +207,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):
@ -210,20 +228,28 @@ class InstrumentedServer:
t = time.time()
self.sio.manager._timestamps[sid] = t
serialized_socket = self.serialize_socket(sid, namespace, eio_sid)
self.sio.emit('socket_connected', (
serialized_socket,
datetime.utcfromtimestamp(t).isoformat() + 'Z',
), namespace=self.admin_namespace)
self.sio.emit(
"socket_connected",
(
serialized_socket,
datetime.utcfromtimestamp(t).isoformat() + "Z",
),
namespace=self.admin_namespace,
)
return sid
def _disconnect(self, sid, namespace, **kwargs):
del self.sio.manager._timestamps[sid]
self.sio.emit('socket_disconnected', (
namespace,
sid,
'N/A',
datetime.utcnow().isoformat() + 'Z',
), namespace=self.admin_namespace)
self.sio.emit(
"socket_disconnected",
(
namespace,
sid,
"N/A",
datetime.utcnow().isoformat() + "Z",
),
namespace=self.admin_namespace,
)
return self.sio.manager.__disconnect(sid, namespace, **kwargs)
def _check_for_upgrade(self, eio_sid, sid, namespace): # pragma: no cover
@ -231,106 +257,130 @@ 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.utcnow().isoformat() + 'Z',
), namespace=self.admin_namespace)
self.sio.emit(
"room_joined",
(
namespace,
room,
sid,
datetime.utcnow().isoformat() + "Z",
),
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.utcnow().isoformat() + 'Z',
), namespace=self.admin_namespace)
self.sio.emit(
"room_left",
(
namespace,
room,
sid,
datetime.utcnow().isoformat() + "Z",
),
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 [data]
event_data = [event] + list(data) if isinstance(data, tuple) else [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.utcnow().isoformat() + 'Z',
), namespace=self.admin_namespace)
self.sio.emit(
"event_sent",
(
namespace,
sid,
event_data,
datetime.utcnow().isoformat() + "Z",
),
namespace=self.admin_namespace,
)
return ret
def _handle_event_internal(self, server, sid, eio_sid, data, namespace,
id):
ret = self.sio.__handle_event_internal(server, sid, eio_sid, data,
namespace, id)
self.sio.emit('event_received', (
namespace,
sid,
data,
datetime.utcnow().isoformat() + 'Z',
), namespace=self.admin_namespace)
def _handle_event_internal(self, server, sid, eio_sid, data, namespace, id):
ret = self.sio.__handle_event_internal(
server, sid, eio_sid, data, namespace, id
)
self.sio.emit(
"event_received",
(
namespace,
sid,
data,
datetime.utcnow().isoformat() + "Z",
),
namespace=self.admin_namespace,
)
return ret
def _handle_eio_connect(self, eio_sid, environ):
if self.stop_stats_event is None:
self.stop_stats_event = self.sio.eio.create_event()
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):
self.event_buffer.push('rawDisconnection')
self.event_buffer.push("rawDisconnection")
return self.sio._handle_eio_disconnect(eio_sid)
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
@ -345,12 +395,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.utcfromtimestamp(t).isoformat() + 'Z',
), namespace=self.admin_namespace)
serialized_socket = self.serialize_socket(sid, namespace, eio_sid)
self.sio.emit(
"socket_connected",
(
serialized_socket,
datetime.utcfromtimestamp(t).isoformat() + "Z",
),
namespace=self.admin_namespace,
)
return socket.__send_ping()
def _emit_server_stats(self):
@ -359,47 +412,64 @@ 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.utcfromtimestamp(tm).isoformat() + 'Z'
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.utcfromtimestamp(tm).isoformat() + "Z" if tm else "",
},
'rooms': self.sio.manager.get_rooms(sid, namespace),
"rooms": self.sio.manager.get_rooms(sid, namespace),
}

25
src/socketio/asgi.py

@ -33,10 +33,21 @@ class ASGIApp(engineio.ASGIApp): # pragma: no cover
})
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,
)

399
src/socketio/async_admin.py

@ -13,19 +13,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 <https://socket.io/docs/v4/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 +50,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 = {}
@ -67,13 +77,11 @@ class InstrumentedAsyncServer:
self.sio.manager.disconnect = self._disconnect
# 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
@ -85,44 +93,47 @@ class InstrumentedAsyncServer:
self.sio._handle_event_internal = self._handle_event_internal
# report engine.io connections
self.sio.eio.on('connect', self._handle_eio_connect)
self.sio.eio.on('disconnect', self._handle_eio_disconnect)
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.manager.connect = self.sio.manager.__connect
self.sio.manager.disconnect = self.sio.manager.__disconnect
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._handle_event_internal = self.sio.__handle_event_internal
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):
@ -139,53 +150,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):
@ -198,128 +215,161 @@ class InstrumentedAsyncServer:
t = time.time()
self.sio.manager._timestamps[sid] = t
serialized_socket = self.serialize_socket(sid, namespace, eio_sid)
await self.sio.emit('socket_connected', (
serialized_socket,
datetime.utcfromtimestamp(t).isoformat() + 'Z',
), namespace=self.admin_namespace)
await self.sio.emit(
"socket_connected",
(
serialized_socket,
datetime.utcfromtimestamp(t).isoformat() + "Z",
),
namespace=self.admin_namespace,
)
return sid
async def _disconnect(self, sid, namespace, **kwargs):
del self.sio.manager._timestamps[sid]
await self.sio.emit('socket_disconnected', (
namespace,
sid,
'N/A',
datetime.utcnow().isoformat() + 'Z',
), namespace=self.admin_namespace)
await self.sio.emit(
"socket_disconnected",
(
namespace,
sid,
"N/A",
datetime.utcnow().isoformat() + "Z",
),
namespace=self.admin_namespace,
)
return await self.sio.manager.__disconnect(sid, namespace, **kwargs)
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.utcnow().isoformat() + 'Z',
)))
self.admin_queue.append(
(
"room_joined",
(
namespace,
room,
sid,
datetime.utcnow().isoformat() + "Z",
),
)
)
return ret
def _basic_leave_room(self, sid, namespace, room):
if room:
self.admin_queue.append(('room_left', (
namespace,
room,
sid,
datetime.utcnow().isoformat() + 'Z',
)))
self.admin_queue.append(
(
"room_left",
(
namespace,
room,
sid,
datetime.utcnow().isoformat() + "Z",
),
)
)
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 [data]
event_data = [event] + list(data) if isinstance(data, tuple) else [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.utcnow().isoformat() + 'Z',
), namespace=self.admin_namespace)
await self.sio.emit(
"event_sent",
(
namespace,
sid,
event_data,
datetime.utcnow().isoformat() + "Z",
),
namespace=self.admin_namespace,
)
return ret
async def _handle_event_internal(self, server, sid, eio_sid, data,
namespace, id):
ret = await self.sio.__handle_event_internal(server, sid, eio_sid,
data, namespace, id)
await self.sio.emit('event_received', (
namespace,
sid,
data,
datetime.utcnow().isoformat() + 'Z',
), namespace=self.admin_namespace)
async def _handle_event_internal(self, server, sid, eio_sid, data, namespace, id):
ret = await self.sio.__handle_event_internal(
server, sid, eio_sid, data, namespace, id
)
await self.sio.emit(
"event_received",
(
namespace,
sid,
data,
datetime.utcnow().isoformat() + "Z",
),
namespace=self.admin_namespace,
)
return ret
async def _handle_eio_connect(self, eio_sid, environ):
if self.stop_stats_event is None:
self.stop_stats_event = self.sio.eio.create_event()
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):
self.event_buffer.push('rawDisconnection')
self.event_buffer.push("rawDisconnection")
return await self.sio._handle_eio_disconnect(eio_sid)
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
@ -334,12 +384,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.utcfromtimestamp(t).isoformat() + 'Z',
), namespace=self.admin_namespace)
serialized_socket = self.serialize_socket(sid, namespace, eio_sid)
await self.sio.emit(
"socket_connected",
(
serialized_socket,
datetime.utcfromtimestamp(t).isoformat() + "Z",
),
namespace=self.admin_namespace,
)
return await socket.__send_ping()
async def _emit_server_stats(self):
@ -348,51 +401,67 @@ 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.utcfromtimestamp(tm).isoformat() + 'Z'
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.utcfromtimestamp(tm).isoformat() + "Z" if tm else "",
},
'rooms': self.sio.manager.get_rooms(sid, namespace),
"rooms": self.sio.manager.get_rooms(sid, namespace),
}

52
src/socketio/async_aiopika_manager.py

@ -35,14 +35,21 @@ class AsyncAioPikaManager(AsyncPubSubManager): # pragma: no cover
and receiving.
"""
name = 'asyncaiopika'
def __init__(self, url='amqp://guest:guest@localhost:5672//',
channel='socketio', write_only=False, logger=None):
name = "asyncaiopika"
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)."
)
self.url = url
self._lock = asyncio.Lock()
self.publisher_connection = None
@ -57,12 +64,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 +92,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 +127,10 @@ 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... " "retrying in {} secs".format(
retry_sleep
)
)
await asyncio.sleep(retry_sleep)
retry_sleep = min(retry_sleep * 2, 60)
except aio_pika.exceptions.ChannelInvalidStateError:

186
src/socketio/async_client.py

@ -8,7 +8,7 @@ from . import base_client
from . import exceptions
from . import packet
default_logger = logging.getLogger('socketio.client')
default_logger = logging.getLogger("socketio.client")
class AsyncClient(base_client.BaseClient):
@ -64,12 +64,21 @@ 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):
async def connect(
self,
url,
headers={},
auth=None,
transports=None,
namespaces=None,
socketio_path="socket.io",
wait=True,
wait_timeout=1,
):
"""Connect to a Socket.IO server.
:param url: The URL of the Socket.IO server. It can include custom
@ -114,7 +123,7 @@ class AsyncClient(base_client.BaseClient):
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
@ -124,10 +133,11 @@ 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())))
namespaces = list(
set(self.handlers.keys()).union(set(self.namespace_handlers.keys()))
)
if len(namespaces) == 0:
namespaces = ['/']
namespaces = ["/"]
elif isinstance(namespaces, str):
namespaces = [namespaces]
self.connection_namespaces = namespaces
@ -139,21 +149,25 @@ 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],
)
raise exceptions.ConnectionError(exc.args[0]) from None
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
@ -162,7 +176,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
@ -180,7 +195,7 @@ class AsyncClient(base_client.BaseClient):
if not self._reconnect_task:
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):
@ -209,10 +224,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)
@ -226,8 +242,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.
@ -249,8 +268,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.
@ -290,15 +308,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.
@ -308,8 +329,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(abort=True)
def start_background_task(self, target, *args, **kwargs):
@ -358,19 +378,19 @@ 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('Namespace {} is connected'.format(namespace))
self.namespaces[namespace] = (data or {}).get('sid', self.sid)
await self._trigger_event('connect', namespace=namespace)
self.logger.info("Namespace {} is connected".format(namespace))
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=namespace)
await self._trigger_event('__disconnect_final', namespace=namespace)
namespace = namespace or "/"
await self._trigger_event("disconnect", namespace=namespace)
await self._trigger_event("__disconnect_final", namespace=namespace)
if namespace in self.namespaces:
del self.namespaces[namespace]
if not self.namespaces:
@ -378,7 +398,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:
@ -390,18 +410,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:
@ -411,18 +432,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("Connection to namespace {} was rejected".format(namespace))
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
@ -433,9 +453,8 @@ class AsyncClient(base_client.BaseClient):
handler = None
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:
if asyncio.iscoroutinefunction(handler):
@ -449,8 +468,7 @@ class AsyncClient(base_client.BaseClient):
# or else, forward the event to a namepsace handler if one exists
elif namespace in self.namespace_handlers:
return await self.namespace_handlers[namespace].trigger_event(
event, *args)
return await self.namespace_handlers[namespace].trigger_event(event, *args)
async def _handle_reconnect(self):
if self._reconnect_abort is None: # pragma: no cover
@ -466,49 +484,51 @@ class AsyncClient(base_client.BaseClient):
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))
"Connection failed, new attempt in {:.02f} seconds".format(delay)
)
try:
await asyncio.wait_for(self._reconnect_abort.wait(), delay)
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
except (asyncio.TimeoutError, asyncio.CancelledError):
pass
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)
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,
)
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."""
@ -530,32 +550,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):
"""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', namespace=n)
await self._trigger_event("disconnect", namespace=n)
if not will_reconnect:
await self._trigger_event('__disconnect_final',
namespace=n)
await self._trigger_event("__disconnect_final", namespace=n)
self.namespaces = {}
self.connected = False
self.callbacks = {}
self._binary_packet = None
self.sid = None
if will_reconnect:
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

30
src/socketio/async_manager.py

@ -7,11 +7,13 @@ 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, **kwargs):
async def emit(
self, event, data, namespace, room=None, skip_sid=None, callback=None, **kwargs
):
"""Emit a message to a single client, a room, or all the clients
connected to the namespace.
@ -34,17 +36,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
@ -54,10 +59,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)
@ -107,7 +113,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:

125
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
handler_name = "on_" + event
if hasattr(self, handler_name):
handler = getattr(self, handler_name)
if asyncio.iscoroutinefunction(handler) is True:
@ -41,8 +42,17 @@ class AsyncNamespace(base_namespace.BaseServerNamespace):
ret = handler(*args)
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
@ -51,14 +61,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
@ -67,24 +90,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.
@ -96,7 +136,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.
@ -108,7 +149,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.
@ -119,8 +161,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.
@ -131,8 +172,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.
@ -144,7 +184,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.
@ -164,8 +205,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):
@ -181,6 +221,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
@ -194,7 +235,7 @@ class AsyncClientNamespace(base_namespace.BaseClientNamespace):
Note: this method is a coroutine.
"""
handler_name = 'on_' + event
handler_name = "on_" + event
if hasattr(self, handler_name):
handler = getattr(self, handler_name)
if asyncio.iscoroutinefunction(handler) is True:
@ -215,9 +256,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.
@ -228,9 +269,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.
@ -239,9 +280,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.

210
src/socketio/async_pubsub_manager.py

@ -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,18 @@ 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, **kwargs):
async def emit(
self,
event,
data,
namespace=None,
room=None,
skip_sid=None,
callback=None,
**kwargs,
):
"""Emit a message to a single client, a room, or all the clients
connected to the namespace.
@ -49,25 +58,37 @@ class AsyncPubSubManager(AsyncManager):
Note: this method is a coroutine.
"""
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
@ -77,27 +98,39 @@ class AsyncPubSubManager(AsyncManager):
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})
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)
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}
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):
@ -105,13 +138,22 @@ class AsyncPubSubManager(AsyncManager):
# 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}
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
@ -121,8 +163,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,
@ -131,67 +174,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:
@ -211,30 +266,33 @@ 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:
self.server.logger.exception(
'Unknown error in pubsub listening task')
"Unknown error in pubsub listening task"
)
except asyncio.CancelledError: # pragma: no cover
break
except: # pragma: no cover
import traceback
traceback.print_exc()

54
src/socketio/async_redis_manager.py

@ -39,23 +39,31 @@ class AsyncRedisManager(AsyncPubSubManager): # pragma: no cover
:param redis_options: additional keyword arguments to be passed to
``aioredis.from_url()``.
"""
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.")
self.redis_url = url
self.redis_options = redis_options or {}
self._redis_connect()
super().__init__(channel=channel, write_only=write_only, logger=logger)
def _redis_connect(self):
self.redis = aioredis.Redis.from_url(self.redis_url,
**self.redis_options)
self.redis = aioredis.Redis.from_url(self.redis_url, **self.redis_options)
self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
async def _publish(self, data):
@ -64,16 +72,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):
@ -88,9 +93,11 @@ 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 " "{} secs".format(
retry_sleep
)
)
connect = True
await asyncio.sleep(retry_sleep)
retry_sleep *= 2
@ -98,10 +105,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)

306
src/socketio/async_server.py

@ -101,23 +101,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
@ -161,16 +183,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
@ -208,12 +245,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
@ -254,10 +306,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 = []
@ -265,15 +316,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.
@ -289,8 +350,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):
@ -305,8 +366,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):
@ -320,8 +381,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):
@ -335,7 +396,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, {})
@ -348,7 +409,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
@ -375,6 +436,7 @@ class AsyncServer(base_server.BaseServer):
async with eio.session(sid) as session:
print('received message from ', session['username'])
"""
class _session_context_manager(object):
def __init__(self, server, sid, namespace):
self.server = server
@ -384,12 +446,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)
@ -407,19 +471,19 @@ 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)
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)
await self.manager.disconnect(sid, namespace=namespace, ignore_queue=True)
async def shutdown(self):
"""Stop Socket.IO background tasks.
@ -427,7 +491,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):
@ -467,9 +531,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 <https://socket.io/docs/v4/admin-ui/>`_.
@ -501,10 +571,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."""
@ -521,32 +597,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
@ -554,46 +642,52 @@ 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):
"""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)
await self._trigger_event("disconnect", namespace, sid)
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:
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:
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
@ -604,14 +698,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):
@ -621,9 +717,8 @@ class AsyncServer(base_server.BaseServer):
handler = None
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:
if asyncio.iscoroutinefunction(handler):
@ -639,8 +734,7 @@ class AsyncServer(base_server.BaseServer):
# or else, forward the event to a namepsace handler if one exists
elif namespace in self.namespace_handlers: # pragma: no branch
return await self.namespace_handlers[namespace].trigger_event(
event, *args)
return await self.namespace_handlers[namespace].trigger_event(event, *args)
async def _handle_eio_connect(self, eio_sid, environ):
"""Handle the Engine.IO connection event."""
@ -656,11 +750,9 @@ 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:
@ -668,18 +760,18 @@ class AsyncServer(base_server.BaseServer):
elif pkt.packet_type == packet.DISCONNECT:
await self._handle_disconnect(eio_sid, pkt.namespace)
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):
"""Handle Engine.IO disconnect event."""

48
src/socketio/async_simple_client.py

@ -12,19 +12,27 @@ class AsyncSimpleClient:
Th positional and keyword arguments given in the constructor are passed
to the underlying :func:`socketio.AsyncClient` object.
"""
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
@ -56,7 +64,7 @@ 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()
@ -76,15 +84,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):
@ -102,7 +115,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.
@ -127,8 +140,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
@ -157,9 +169,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
@ -178,15 +190,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()

69
src/socketio/base_client.py

@ -6,7 +6,7 @@ import threading
from . import base_namespace
from . import packet
default_logger = logging.getLogger('socketio.client')
default_logger = logging.getLogger("socketio.client")
reconnecting_clients = []
@ -29,18 +29,28 @@ original_signal_handler = None
class BaseClient:
reserved_events = ['connect', 'connect_error', 'disconnect',
'__disconnect_final']
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):
reserved_events = ["connect", "connect_error", "disconnect", "__disconnect_final"]
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
@ -49,25 +59,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
@ -134,7 +145,7 @@ class BaseClient:
function if it exists. The ``'disconnect'`` handler does not take
arguments.
"""
namespace = namespace or '/'
namespace = namespace or "/"
def set_handler(handler):
if namespace not in self.handlers:
@ -188,14 +199,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.
@ -209,7 +218,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.
@ -221,7 +230,7 @@ class BaseClient:
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])

18
src/socketio/base_manager.py

@ -3,7 +3,7 @@ import logging
from bidict import bidict, ValueDuplicationError
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 {})
@ -52,8 +52,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:
@ -95,15 +97,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]:

2
src/socketio/base_namespace.py

@ -1,6 +1,6 @@
class BaseNamespace(object):
def __init__(self, namespace=None):
self.namespace = namespace or '/'
self.namespace = namespace or "/"
def is_asyncio_based(self):
return False

59
src/socketio/base_server.py

@ -4,34 +4,43 @@ from . import manager
from . import base_namespace
from . import packet
default_logger = logging.getLogger('socketio.server')
default_logger = logging.getLogger("socketio.server")
class BaseServer:
reserved_events = ['connect', 'disconnect']
def __init__(self, client_manager=None, logger=False, serializer='default',
json=None, async_handlers=True, always_connect=False,
namespaces=None, **kwargs):
reserved_events = ["connect", "disconnect"]
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 = {}
@ -59,7 +68,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
@ -103,7 +112,7 @@ class BaseServer:
client's acknowledgement callback function if it exists. The
``'disconnect'`` handler does not take a second argument.
"""
namespace = namespace or '/'
namespace = namespace or "/"
def set_handler(handler):
if namespace not in self.handlers:
@ -157,14 +166,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.
@ -173,7 +180,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):
@ -193,7 +200,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 _handle_eio_connect(self): # pragma: no cover
@ -206,4 +213,4 @@ class BaseServer:
raise NotImplementedError()
def _engineio_server_class(self): # pragma: no cover
raise NotImplementedError('Must be implemented in subclasses')
raise NotImplementedError("Must be implemented in subclasses")

178
src/socketio/client.py

@ -67,9 +67,18 @@ 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):
def connect(
self,
url,
headers={},
auth=None,
transports=None,
namespaces=None,
socketio_path="socket.io",
wait=True,
wait_timeout=1,
):
"""Connect to a Socket.IO server.
:param url: The URL of the Socket.IO server. It can include custom
@ -112,7 +121,7 @@ class Client(base_client.BaseClient):
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
@ -122,10 +131,11 @@ 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())))
namespaces = list(
set(self.handlers.keys()).union(set(self.namespace_handlers.keys()))
)
if len(namespaces) == 0:
namespaces = ['/']
namespaces = ["/"]
elif isinstance(namespaces, str):
namespaces = [namespaces]
self.connection_namespaces = namespaces
@ -137,14 +147,19 @@ 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],
)
raise exceptions.ConnectionError(exc.args[0]) from None
if wait:
@ -155,7 +170,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
@ -171,7 +187,7 @@ class Client(base_client.BaseClient):
if not self._reconnect_task:
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):
@ -198,10 +214,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)
@ -215,8 +232,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.
@ -236,8 +256,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.
@ -275,21 +294,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(abort=True)
def start_background_task(self, target, *args, **kwargs):
@ -336,19 +357,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('Namespace {} is connected'.format(namespace))
self.namespaces[namespace] = (data or {}).get('sid', self.sid)
self._trigger_event('connect', namespace=namespace)
self.logger.info("Namespace {} is connected".format(namespace))
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=namespace)
self._trigger_event('__disconnect_final', namespace=namespace)
namespace = namespace or "/"
self._trigger_event("disconnect", namespace=namespace)
self._trigger_event("__disconnect_final", namespace=namespace)
if namespace in self.namespaces:
del self.namespaces[namespace]
if not self.namespaces:
@ -356,7 +377,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:
@ -368,36 +389,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("Connection to namespace {} was rejected".format(namespace))
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
@ -407,14 +428,12 @@ class Client(base_client.BaseClient):
if namespace in self.handlers:
if event in self.handlers[namespace]:
return self.handlers[namespace][event](*args)
elif event not in self.reserved_events and \
'*' in self.handlers[namespace]:
return self.handlers[namespace]['*'](event, *args)
elif event not in self.reserved_events and "*" in self.handlers[namespace]:
return self.handlers[namespace]["*"](event, *args)
# or else, forward the event to a namespace handler if one exists
elif namespace in self.namespace_handlers:
return self.namespace_handlers[namespace].trigger_event(
event, *args)
return self.namespace_handlers[namespace].trigger_event(event, *args)
def _handle_reconnect(self):
if self._reconnect_abort is None: # pragma: no cover
@ -430,44 +449,48 @@ class Client(base_client.BaseClient):
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))
"Connection failed, new attempt in {:.02f} seconds".format(delay)
)
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)
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,
)
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."""
@ -489,31 +512,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):
"""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', namespace=n)
self._trigger_event("disconnect", namespace=n)
if not will_reconnect:
self._trigger_event('__disconnect_final', namespace=n)
self._trigger_event("__disconnect_final", namespace=n)
self.namespaces = {}
self.connected = False
self.callbacks = {}
self._binary_packet = None
self.sid = None
if will_reconnect:
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

11
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):

28
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
@ -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))

69
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 = 'flask-socketio.' + str(uuid.uuid4())
options = {'durable': False, 'queue_arguments': {'x-expires': 300000}}
queue_name = "flask-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,9 @@ 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... " "retrying in {} secs".format(
retry_sleep
)
)
time.sleep(retry_sleep)
retry_sleep = min(retry_sleep * 2, 60)

20
src/socketio/manager.py

@ -4,7 +4,7 @@ from engineio import packet as eio_packet
from . import base_manager
from . import packet
default_logger = logging.getLogger('socketio')
default_logger = logging.getLogger("socketio")
class Manager(base_manager.BaseManager):
@ -16,11 +16,13 @@ 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, **kwargs):
def emit(
self, event, data, namespace, room=None, skip_sid=None, callback=None, **kwargs
):
"""Emit a message to a single client, a room, or all the clients
connected to the namespace."""
if namespace not in self.rooms:
@ -39,12 +41,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:
@ -58,8 +60,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):
@ -85,7 +87,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:

18
src/socketio/middleware.py

@ -27,14 +27,20 @@ class WSGIApp(engineio.WSGIApp):
app = socketio.WSGIApp(sio, wsgi_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)

8
src/socketio/msgpack_packet.py

@ -12,7 +12,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"]

120
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,46 +22,89 @@ 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
handler_name = "on_" + event
if hasattr(self, handler_name):
return getattr(self, handler_name)(*args)
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.
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.
@ -69,8 +113,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.
@ -79,8 +122,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.
@ -89,8 +131,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.
@ -99,8 +140,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.
@ -110,7 +150,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.
@ -128,8 +169,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):
@ -144,6 +184,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.
@ -152,7 +193,7 @@ 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
handler_name = "on_" + event
if hasattr(self, handler_name):
return getattr(self, handler_name)(*args)
@ -163,9 +204,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.
@ -174,8 +215,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.
@ -184,9 +226,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.

124
src/socketio/packet.py

@ -1,10 +1,24 @@
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(object):
@ -22,21 +36,28 @@ class Packet(object):
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 +73,16 @@ class Packet(object):
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 +98,26 @@ class Packet(object):
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 +130,14 @@ class Packet(object):
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,20 +148,21 @@ class Packet(object):
"""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]
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']]
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()}
return {
key: self._reconstruct_binary_internal(value, attachments)
for key, value in data.items()
}
else:
return data
@ -153,13 +175,16 @@ class Packet(object):
def _deconstruct_binary_internal(self, data, attachments):
if isinstance(data, bytes):
attachments.append(data)
return {'_placeholder': True, 'num': len(attachments) - 1}
return {"_placeholder": True, "num": len(attachments) - 1}
elif isinstance(data, list):
return [self._deconstruct_binary_internal(item, attachments)
for item in data]
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()}
return {
key: self._deconstruct_binary_internal(value, attachments)
for key, value in data.items()
}
else:
return data
@ -169,22 +194,25 @@ class Packet(object):
return True
elif isinstance(data, list):
return functools.reduce(
lambda a, b: a or b, [self._data_is_binary(item)
for item in data], False)
lambda a, b: a or b,
[self._data_is_binary(item) for item in data],
False,
)
elif isinstance(data, dict):
return functools.reduce(
lambda a, b: a or b, [self._data_is_binary(item)
for item in data.values()],
False)
lambda a, b: a or b,
[self._data_is_binary(item) for item in data.values()],
False,
)
else:
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:
d['id'] = self.id
d["id"] = self.id
return d

192
src/socketio/pubsub_manager.py

@ -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,18 @@ 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, **kwargs):
def emit(
self,
event,
data,
namespace=None,
room=None,
skip_sid=None,
callback=None,
**kwargs,
):
"""Emit a message to a single client, a room, or all the clients
connected to the namespace.
@ -46,25 +55,37 @@ class PubSubManager(Manager):
The parameters are the same as in :meth:`.Server.emit`.
"""
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
@ -74,16 +95,24 @@ class PubSubManager(Manager):
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}
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,8 +121,13 @@ class PubSubManager(Manager):
# 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}
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):
@ -101,13 +135,22 @@ class PubSubManager(Manager):
# 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}
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
@ -117,8 +160,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,
@ -127,31 +171,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)
@ -162,30 +210,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):
for message in self._listen():
@ -203,23 +259,23 @@ 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:
self.server.logger.exception(
'Unknown error in pubsub listening thread')
"Unknown error in pubsub listening thread"
)

63
src/socketio/redis_manager.py

@ -9,7 +9,7 @@ except ImportError:
from .pubsub_manager import PubSubManager
logger = logging.getLogger('socketio')
logger = logging.getLogger("socketio")
class RedisManager(PubSubManager): # pragma: no cover
@ -37,14 +37,23 @@ class RedisManager(PubSubManager): # pragma: no cover
:param redis_options: additional keyword arguments to be passed to
``Redis.from_url()``.
"""
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)."
)
self.redis_url = url
self.redis_options = redis_options or {}
self._redis_connect()
@ -54,20 +63,22 @@ 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):
self.redis = redis.Redis.from_url(self.redis_url,
**self.redis_options)
self.redis = redis.Redis.from_url(self.redis_url, **self.redis_options)
self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
def _publish(self, data):
@ -79,10 +90,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):
@ -97,8 +108,11 @@ class RedisManager(PubSubManager): # pragma: no cover
for message in self.pubsub.listen():
yield message
except redis.exceptions.RedisError:
logger.error('Cannot receive from redis... '
'retrying in {} secs'.format(retry_sleep))
logger.error(
"Cannot receive from redis... " "retrying in {} secs".format(
retry_sleep
)
)
connect = True
time.sleep(retry_sleep)
retry_sleep *= 2
@ -106,10 +120,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)

273
src/socketio/server.py

@ -6,7 +6,7 @@ from . import base_server
from . import exceptions
from . import packet
default_logger = logging.getLogger('socketio.server')
default_logger = logging.getLogger("socketio.server")
class Server(base_server.BaseServer):
@ -110,8 +110,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
@ -154,16 +164,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
@ -200,12 +225,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
@ -244,10 +284,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 = []
@ -255,13 +294,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.
@ -275,8 +324,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):
@ -289,8 +338,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):
@ -302,8 +351,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):
@ -318,7 +367,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, {})
@ -331,7 +380,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
@ -358,6 +407,7 @@ class Server(base_server.BaseServer):
with sio.session(sid) as session:
print('received message from ', session['username'])
"""
class _session_context_manager(object):
def __init__(self, server, sid, namespace):
self.server = server
@ -366,13 +416,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)
@ -388,19 +436,19 @@ 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.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.manager.disconnect(sid, namespace=namespace, ignore_queue=True)
def shutdown(self):
"""Stop Socket.IO background tasks.
@ -408,7 +456,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):
@ -454,9 +502,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 <https://socket.io/docs/v4/admin-ui/>`_.
@ -488,10 +542,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."""
@ -508,32 +568,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
@ -541,46 +613,52 @@ 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):
"""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)
self._trigger_event("disconnect", namespace, sid)
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
@ -591,14 +669,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):
@ -607,16 +687,14 @@ class Server(base_server.BaseServer):
if namespace in self.handlers:
if event in self.handlers[namespace]:
return self.handlers[namespace][event](*args)
elif event not in self.reserved_events and \
'*' in self.handlers[namespace]:
return self.handlers[namespace]['*'](event, *args)
elif event not in self.reserved_events and "*" in self.handlers[namespace]:
return self.handlers[namespace]["*"](event, *args)
else:
return self.not_handled
# or else, forward the event to a namespace handler if one exists
elif namespace in self.namespace_handlers: # pragma: no branch
return self.namespace_handlers[namespace].trigger_event(
event, *args)
return self.namespace_handlers[namespace].trigger_event(event, *args)
def _handle_eio_connect(self, eio_sid, environ):
"""Handle the Engine.IO connection event."""
@ -632,8 +710,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:
@ -646,13 +723,15 @@ class Server(base_server.BaseServer):
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):
"""Handle Engine.IO disconnect event."""

42
src/socketio/simple_client.py

@ -12,18 +12,27 @@ class SimpleClient:
Th positional and keyword arguments given in the constructor are passed
to the underlying :func:`socketio.Client` object.
"""
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
@ -54,7 +63,7 @@ class SimpleClient:
seconds.
"""
if self.connected:
raise RuntimeError('Already connected')
raise RuntimeError("Already connected")
self.namespace = namespace
self.input_buffer = []
self.input_event.clear()
@ -74,15 +83,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):
@ -100,7 +114,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.
@ -150,8 +164,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
@ -167,8 +182,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()

6
src/socketio/tornado.py

@ -1,8 +1,10 @@
import sys
if sys.version_info >= (3, 5):
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

58
src/socketio/zmq_manager.py

@ -40,33 +40,39 @@ 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).')
r = re.compile(r':\d+\+\d+$')
if not (url.startswith('zmq+tcp://') and r.search(url)):
raise RuntimeError('unexpected connection string: ' + url)
url = url.replace('zmq+', '')
(sink_url, sub_port) = url.split('+')
sink_port = sink_url.split(':')[-1]
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)
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, u'')
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

263
tests/async/test_admin.py

@ -4,6 +4,7 @@ import time
from unittest import mock
import unittest
import pytest
try:
from engineio.async_socket import AsyncSocket as EngineIOSocket
except ImportError:
@ -20,10 +21,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):
@ -33,7 +35,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
@ -41,8 +43,8 @@ def with_instrumented_server(auth=False, **ikwargs):
await instrumented_server.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
instrumented_server = sio.instrument(auth=auth, **ikwargs)
server = SocketIOWebServer(sio, on_shutdown=shutdown)
@ -68,107 +70,115 @@ 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(unittest.TestCase):
def setUp(self):
print('threads at start:', threading.enumerate())
print("threads at start:", threading.enumerate())
self.thread_count = threading.active_count()
def tearDown(self):
print('threads at end:', threading.enumerate())
print("threads at end:", threading.enumerate())
assert self.thread_count == threading.active_count()
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, isvr):
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, isvr):
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, isvr):
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, isvr):
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, isvr):
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, isvr):
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
expected = ['config', 'all_sockets', 'server_stats']
expected = ["config", "all_sockets", "server_stats"]
events = {}
while expected:
data = admin_client.receive(timeout=5)
@ -176,46 +186,45 @@ class TestAsyncAdmin(unittest.TestCase):
events[data[0]] = data[1]
expected.remove(data[0])
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']
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, isvr):
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 = isvr._check_for_upgrade
isvr._check_for_upgrade = AsyncMock()
client2.connect('http://localhost:8900', namespace='/foo',
transports=['polling'])
client2.connect(
"http://localhost:8900", namespace="/foo", transports=["polling"]
)
sid2 = client2.sid
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
expected = ['config', 'all_sockets', 'server_stats']
expected = ["config", "all_sockets", "server_stats"]
events = {}
while expected:
data = admin_client.receive(timeout=5)
@ -223,36 +232,37 @@ class TestAsyncAdmin(unittest.TestCase):
events[data[0]] = data[1]
expected.remove(data[0])
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)
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, isvr):
with socketio.SimpleClient() as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin')
expected = ['config', 'server_stats']
admin_client.connect("http://localhost:8900", namespace="/admin")
expected = ["config", "server_stats"]
events = {}
while expected:
data = admin_client.receive(timeout=5)
@ -260,50 +270,49 @@ class TestAsyncAdmin(unittest.TestCase):
events[data[0]] = data[1]
expected.remove(data[0])
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']
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, isvr):
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(
'emit', ('/', 'room', 'foo', {'bar': 'baz'}))
admin_client.emit("join", ("/", "room", client1.sid))
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

688
tests/async/test_client.py

File diff suppressed because it is too large

450
tests/async/test_manager.py

@ -7,7 +7,7 @@ from socketio import packet
from .helpers import AsyncMock, _run
@unittest.skipIf(sys.version_info < (3, 5), 'only for Python 3.5+')
@unittest.skipIf(sys.version_info < (3, 5), "only for Python 3.5+")
class TestAsyncManager(unittest.TestCase):
def setUp(self):
id = 0
@ -27,383 +27,327 @@ class TestAsyncManager(unittest.TestCase):
self.bm.initialize()
def test_connect(self):
sid = _run(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 = _run(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
def test_pre_disconnect(self):
sid1 = _run(self.bm.connect('123', '/foo'))
sid2 = _run(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')
_run(self.bm.disconnect(sid1, '/foo'))
assert self.bm.pending_disconnect == {'/foo': [sid2]}
_run(self.bm.disconnect(sid2, '/foo'))
sid1 = _run(self.bm.connect("123", "/foo"))
sid2 = _run(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")
_run(self.bm.disconnect(sid1, "/foo"))
assert self.bm.pending_disconnect == {"/foo": [sid2]}
_run(self.bm.disconnect(sid2, "/foo"))
assert self.bm.pending_disconnect == {}
def test_disconnect(self):
sid1 = _run(self.bm.connect('123', '/foo'))
sid2 = _run(self.bm.connect('456', '/foo'))
_run(self.bm.enter_room(sid1, '/foo', 'bar'))
_run(self.bm.enter_room(sid2, '/foo', 'baz'))
_run(self.bm.disconnect(sid1, '/foo'))
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 = _run(self.bm.connect("123", "/foo"))
sid2 = _run(self.bm.connect("456", "/foo"))
_run(self.bm.enter_room(sid1, "/foo", "bar"))
_run(self.bm.enter_room(sid2, "/foo", "baz"))
_run(self.bm.disconnect(sid1, "/foo"))
assert dict(self.bm.rooms["/foo"][None]) == {sid2: "456"}
assert dict(self.bm.rooms["/foo"][sid2]) == {sid2: "456"}
assert dict(self.bm.rooms["/foo"]["baz"]) == {sid2: "456"}
def test_disconnect_default_namespace(self):
sid1 = _run(self.bm.connect('123', '/'))
sid2 = _run(self.bm.connect('123', '/foo'))
sid3 = _run(self.bm.connect('456', '/'))
sid4 = _run(self.bm.connect('456', '/foo'))
assert self.bm.is_connected(sid1, '/')
assert self.bm.is_connected(sid2, '/foo')
assert not self.bm.is_connected(sid2, '/')
assert not self.bm.is_connected(sid1, '/foo')
_run(self.bm.disconnect(sid1, '/'))
assert not self.bm.is_connected(sid1, '/')
assert self.bm.is_connected(sid2, '/foo')
_run(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 = _run(self.bm.connect("123", "/"))
sid2 = _run(self.bm.connect("123", "/foo"))
sid3 = _run(self.bm.connect("456", "/"))
sid4 = _run(self.bm.connect("456", "/foo"))
assert self.bm.is_connected(sid1, "/")
assert self.bm.is_connected(sid2, "/foo")
assert not self.bm.is_connected(sid2, "/")
assert not self.bm.is_connected(sid1, "/foo")
_run(self.bm.disconnect(sid1, "/"))
assert not self.bm.is_connected(sid1, "/")
assert self.bm.is_connected(sid2, "/foo")
_run(self.bm.disconnect(sid2, "/foo"))
assert not self.bm.is_connected(sid2, "/foo")
assert dict(self.bm.rooms["/"][None]) == {sid3: "456"}
assert dict(self.bm.rooms["/"][sid3]) == {sid3: "456"}
assert dict(self.bm.rooms["/foo"][None]) == {sid4: "456"}
assert dict(self.bm.rooms["/foo"][sid4]) == {sid4: "456"}
def test_disconnect_twice(self):
sid1 = _run(self.bm.connect('123', '/'))
sid2 = _run(self.bm.connect('123', '/foo'))
sid3 = _run(self.bm.connect('456', '/'))
sid4 = _run(self.bm.connect('456', '/foo'))
_run(self.bm.disconnect(sid1, '/'))
_run(self.bm.disconnect(sid2, '/foo'))
_run(self.bm.disconnect(sid1, '/'))
_run(self.bm.disconnect(sid2, '/foo'))
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 = _run(self.bm.connect("123", "/"))
sid2 = _run(self.bm.connect("123", "/foo"))
sid3 = _run(self.bm.connect("456", "/"))
sid4 = _run(self.bm.connect("456", "/foo"))
_run(self.bm.disconnect(sid1, "/"))
_run(self.bm.disconnect(sid2, "/foo"))
_run(self.bm.disconnect(sid1, "/"))
_run(self.bm.disconnect(sid2, "/foo"))
assert dict(self.bm.rooms["/"][None]) == {sid3: "456"}
assert dict(self.bm.rooms["/"][sid3]) == {sid3: "456"}
assert dict(self.bm.rooms["/foo"][None]) == {sid4: "456"}
assert dict(self.bm.rooms["/foo"][sid4]) == {sid4: "456"}
def test_disconnect_all(self):
sid1 = _run(self.bm.connect('123', '/foo'))
sid2 = _run(self.bm.connect('456', '/foo'))
_run(self.bm.enter_room(sid1, '/foo', 'bar'))
_run(self.bm.enter_room(sid2, '/foo', 'baz'))
_run(self.bm.disconnect(sid1, '/foo'))
_run(self.bm.disconnect(sid2, '/foo'))
sid1 = _run(self.bm.connect("123", "/foo"))
sid2 = _run(self.bm.connect("456", "/foo"))
_run(self.bm.enter_room(sid1, "/foo", "bar"))
_run(self.bm.enter_room(sid2, "/foo", "baz"))
_run(self.bm.disconnect(sid1, "/foo"))
_run(self.bm.disconnect(sid2, "/foo"))
assert self.bm.rooms == {}
def test_disconnect_with_callbacks(self):
sid1 = _run(self.bm.connect('123', '/'))
sid2 = _run(self.bm.connect('123', '/foo'))
sid3 = _run(self.bm.connect('456', '/foo'))
self.bm._generate_ack_id(sid1, 'f')
self.bm._generate_ack_id(sid2, 'g')
self.bm._generate_ack_id(sid3, 'h')
_run(self.bm.disconnect(sid2, '/foo'))
sid1 = _run(self.bm.connect("123", "/"))
sid2 = _run(self.bm.connect("123", "/foo"))
sid3 = _run(self.bm.connect("456", "/foo"))
self.bm._generate_ack_id(sid1, "f")
self.bm._generate_ack_id(sid2, "g")
self.bm._generate_ack_id(sid3, "h")
_run(self.bm.disconnect(sid2, "/foo"))
assert sid2 not in self.bm.callbacks
_run(self.bm.disconnect(sid1, '/'))
_run(self.bm.disconnect(sid1, "/"))
assert sid1 not in self.bm.callbacks
assert sid3 in self.bm.callbacks
def test_trigger_sync_callback(self):
sid1 = _run(self.bm.connect('123', '/'))
sid2 = _run(self.bm.connect('123', '/foo'))
sid1 = _run(self.bm.connect("123", "/"))
sid2 = _run(self.bm.connect("123", "/foo"))
cb = mock.MagicMock()
id1 = self.bm._generate_ack_id(sid1, cb)
id2 = self.bm._generate_ack_id(sid2, cb)
_run(self.bm.trigger_callback(sid1, id1, ['foo']))
_run(self.bm.trigger_callback(sid2, id2, ['bar', 'baz']))
_run(self.bm.trigger_callback(sid1, id1, ["foo"]))
_run(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")
def test_trigger_async_callback(self):
sid1 = _run(self.bm.connect('123', '/'))
sid2 = _run(self.bm.connect('123', '/foo'))
sid1 = _run(self.bm.connect("123", "/"))
sid2 = _run(self.bm.connect("123", "/foo"))
cb = AsyncMock()
id1 = self.bm._generate_ack_id(sid1, cb)
id2 = self.bm._generate_ack_id(sid2, cb)
_run(self.bm.trigger_callback(sid1, id1, ['foo']))
_run(self.bm.trigger_callback(sid2, id2, ['bar', 'baz']))
_run(self.bm.trigger_callback(sid1, id1, ["foo"]))
_run(self.bm.trigger_callback(sid2, id2, ["bar", "baz"]))
assert cb.mock.call_count == 2
cb.mock.assert_any_call('foo')
cb.mock.assert_any_call('bar', 'baz')
cb.mock.assert_any_call("foo")
cb.mock.assert_any_call("bar", "baz")
def test_invalid_callback(self):
sid = _run(self.bm.connect('123', '/'))
sid = _run(self.bm.connect("123", "/"))
cb = mock.MagicMock()
id = self.bm._generate_ack_id(sid, cb)
# these should not raise an exception
_run(self.bm.trigger_callback('xxx', id, ['foo']))
_run(self.bm.trigger_callback(sid, id + 1, ['foo']))
_run(self.bm.trigger_callback("xxx", id, ["foo"]))
_run(self.bm.trigger_callback(sid, id + 1, ["foo"]))
assert cb.mock.call_count == 0
def test_get_namespaces(self):
assert list(self.bm.get_namespaces()) == []
_run(self.bm.connect('123', '/'))
_run(self.bm.connect('123', '/foo'))
_run(self.bm.connect("123", "/"))
_run(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 = _run(self.bm.connect('123', '/'))
sid2 = _run(self.bm.connect('456', '/'))
sid3 = _run(self.bm.connect('789', '/'))
_run(self.bm.disconnect(sid3, '/'))
assert sid3 not in self.bm.rooms['/'][None]
participants = list(self.bm.get_participants('/', None))
sid1 = _run(self.bm.connect("123", "/"))
sid2 = _run(self.bm.connect("456", "/"))
sid3 = _run(self.bm.connect("789", "/"))
_run(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 = _run(self.bm.connect('123', '/foo'))
_run(self.bm.leave_room(sid, '/foo', 'baz'))
_run(self.bm.leave_room(sid, '/bar', 'baz'))
sid = _run(self.bm.connect("123", "/foo"))
_run(self.bm.leave_room(sid, "/foo", "baz"))
_run(self.bm.leave_room(sid, "/bar", "baz"))
def test_no_room(self):
rooms = self.bm.get_rooms('123', '/foo')
rooms = self.bm.get_rooms("123", "/foo")
assert [] == rooms
def test_close_room(self):
sid = _run(self.bm.connect('123', '/foo'))
_run(self.bm.connect('456', '/foo'))
_run(self.bm.connect('789', '/foo'))
_run(self.bm.enter_room(sid, '/foo', 'bar'))
_run(self.bm.enter_room(sid, '/foo', 'bar'))
_run(self.bm.close_room('bar', '/foo'))
sid = _run(self.bm.connect("123", "/foo"))
_run(self.bm.connect("456", "/foo"))
_run(self.bm.connect("789", "/foo"))
_run(self.bm.enter_room(sid, "/foo", "bar"))
_run(self.bm.enter_room(sid, "/foo", "bar"))
_run(self.bm.close_room("bar", "/foo"))
from pprint import pprint
pprint(self.bm.rooms)
assert 'bar' not in self.bm.rooms['/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 = _run(self.bm.connect('123', '/foo'))
_run(self.bm.enter_room(sid, '/foo', 'bar'))
r = self.bm.get_rooms(sid, '/foo')
sid = _run(self.bm.connect("123", "/foo"))
_run(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 = _run(self.bm.connect('123', '/foo'))
_run(self.bm.connect('456', '/foo'))
_run(
self.bm.emit(
'my event', {'foo': 'bar'}, namespace='/foo', room=sid
)
)
sid = _run(self.bm.connect("123", "/foo"))
_run(self.bm.connect("456", "/foo"))
_run(self.bm.emit("my event", {"foo": "bar"}, namespace="/foo", room=sid))
assert self.bm.server._send_eio_packet.mock.call_count == 1
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] == "123"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]'
def test_emit_to_room(self):
sid1 = _run(self.bm.connect('123', '/foo'))
_run(self.bm.enter_room(sid1, '/foo', 'bar'))
sid2 = _run(self.bm.connect('456', '/foo'))
_run(self.bm.enter_room(sid2, '/foo', 'bar'))
_run(self.bm.connect('789', '/foo'))
_run(
self.bm.emit(
'my event', {'foo': 'bar'}, namespace='/foo', room='bar'
)
)
sid1 = _run(self.bm.connect("123", "/foo"))
_run(self.bm.enter_room(sid1, "/foo", "bar"))
sid2 = _run(self.bm.connect("456", "/foo"))
_run(self.bm.enter_room(sid2, "/foo", "bar"))
_run(self.bm.connect("789", "/foo"))
_run(self.bm.emit("my event", {"foo": "bar"}, namespace="/foo", room="bar"))
assert self.bm.server._send_eio_packet.mock.call_count == 2
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] \
== '456'
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] == "123"
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] == "456"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] \
== pkt
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] == pkt
assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]'
def test_emit_to_rooms(self):
sid1 = _run(self.bm.connect('123', '/foo'))
_run(self.bm.enter_room(sid1, '/foo', 'bar'))
sid2 = _run(self.bm.connect('456', '/foo'))
_run(self.bm.enter_room(sid2, '/foo', 'bar'))
_run(self.bm.enter_room(sid2, '/foo', 'baz'))
sid3 = _run(self.bm.connect('789', '/foo'))
_run(self.bm.enter_room(sid3, '/foo', 'baz'))
sid1 = _run(self.bm.connect("123", "/foo"))
_run(self.bm.enter_room(sid1, "/foo", "bar"))
sid2 = _run(self.bm.connect("456", "/foo"))
_run(self.bm.enter_room(sid2, "/foo", "bar"))
_run(self.bm.enter_room(sid2, "/foo", "baz"))
sid3 = _run(self.bm.connect("789", "/foo"))
_run(self.bm.enter_room(sid3, "/foo", "baz"))
_run(
self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo',
room=['bar', 'baz'])
self.bm.emit(
"my event", {"foo": "bar"}, namespace="/foo", room=["bar", "baz"]
)
)
assert self.bm.server._send_eio_packet.mock.call_count == 3
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] \
== '456'
assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][0] \
== '789'
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] == "123"
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] == "456"
assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][0] == "789"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] \
== pkt
assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][1] \
== pkt
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] == pkt
assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][1] == pkt
assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]'
def test_emit_to_all(self):
sid1 = _run(self.bm.connect('123', '/foo'))
_run(self.bm.enter_room(sid1, '/foo', 'bar'))
sid2 = _run(self.bm.connect('456', '/foo'))
_run(self.bm.enter_room(sid2, '/foo', 'bar'))
_run(self.bm.connect('789', '/foo'))
_run(self.bm.connect('abc', '/bar'))
_run(self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo'))
sid1 = _run(self.bm.connect("123", "/foo"))
_run(self.bm.enter_room(sid1, "/foo", "bar"))
sid2 = _run(self.bm.connect("456", "/foo"))
_run(self.bm.enter_room(sid2, "/foo", "bar"))
_run(self.bm.connect("789", "/foo"))
_run(self.bm.connect("abc", "/bar"))
_run(self.bm.emit("my event", {"foo": "bar"}, namespace="/foo"))
assert self.bm.server._send_eio_packet.mock.call_count == 3
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] \
== '456'
assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][0] \
== '789'
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] == "123"
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] == "456"
assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][0] == "789"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] \
== pkt
assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][1] \
== pkt
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] == pkt
assert self.bm.server._send_eio_packet.mock.call_args_list[2][0][1] == pkt
assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]'
def test_emit_to_all_skip_one(self):
sid1 = _run(self.bm.connect('123', '/foo'))
_run(self.bm.enter_room(sid1, '/foo', 'bar'))
sid2 = _run(self.bm.connect('456', '/foo'))
_run(self.bm.enter_room(sid2, '/foo', 'bar'))
_run(self.bm.connect('789', '/foo'))
_run(self.bm.connect('abc', '/bar'))
_run(
self.bm.emit(
'my event', {'foo': 'bar'}, namespace='/foo', skip_sid=sid2
)
)
sid1 = _run(self.bm.connect("123", "/foo"))
_run(self.bm.enter_room(sid1, "/foo", "bar"))
sid2 = _run(self.bm.connect("456", "/foo"))
_run(self.bm.enter_room(sid2, "/foo", "bar"))
_run(self.bm.connect("789", "/foo"))
_run(self.bm.connect("abc", "/bar"))
_run(self.bm.emit("my event", {"foo": "bar"}, namespace="/foo", skip_sid=sid2))
assert self.bm.server._send_eio_packet.mock.call_count == 2
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] \
== '789'
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] == "123"
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] == "789"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] \
== pkt
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][1] == pkt
assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]'
def test_emit_to_all_skip_two(self):
sid1 = _run(self.bm.connect('123', '/foo'))
_run(self.bm.enter_room(sid1, '/foo', 'bar'))
sid2 = _run(self.bm.connect('456', '/foo'))
_run(self.bm.enter_room(sid2, '/foo', 'bar'))
sid3 = _run(self.bm.connect('789', '/foo'))
_run(self.bm.connect('abc', '/bar'))
sid1 = _run(self.bm.connect("123", "/foo"))
_run(self.bm.enter_room(sid1, "/foo", "bar"))
sid2 = _run(self.bm.connect("456", "/foo"))
_run(self.bm.enter_room(sid2, "/foo", "bar"))
sid3 = _run(self.bm.connect("789", "/foo"))
_run(self.bm.connect("abc", "/bar"))
_run(
self.bm.emit(
'my event',
{'foo': 'bar'},
namespace='/foo',
"my event",
{"foo": "bar"},
namespace="/foo",
skip_sid=[sid1, sid3],
)
)
assert self.bm.server._send_eio_packet.mock.call_count == 1
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '456'
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] == "456"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert pkt.encode() == '42/foo,["my event",{"foo":"bar"}]'
def test_emit_with_callback(self):
sid = _run(self.bm.connect('123', '/foo'))
sid = _run(self.bm.connect("123", "/foo"))
self.bm._generate_ack_id = mock.MagicMock()
self.bm._generate_ack_id.return_value = 11
_run(
self.bm.emit(
'my event', {'foo': 'bar'}, namespace='/foo', callback='cb'
)
)
self.bm._generate_ack_id.assert_called_once_with(sid, 'cb')
_run(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.mock.call_count == 1
assert self.bm.server._send_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.bm.server._send_packet.mock.call_args_list[0][0][0] == "123"
pkt = self.bm.server._send_packet.mock.call_args_list[0][0][1]
assert pkt.encode() == '2/foo,11["my event",{"foo":"bar"}]'
def test_emit_to_invalid_room(self):
_run(
self.bm.emit('my event', {'foo': 'bar'}, namespace='/', room='123')
)
_run(self.bm.emit("my event", {"foo": "bar"}, namespace="/", room="123"))
def test_emit_to_invalid_namespace(self):
_run(self.bm.emit('my event', {'foo': 'bar'}, namespace='/foo'))
_run(self.bm.emit("my event", {"foo": "bar"}, namespace="/foo"))
def test_emit_with_tuple(self):
sid = _run(self.bm.connect('123', '/foo'))
_run(
self.bm.emit(
'my event', ('foo', 'bar'), namespace='/foo', room=sid
)
)
sid = _run(self.bm.connect("123", "/foo"))
_run(self.bm.emit("my event", ("foo", "bar"), namespace="/foo", room=sid))
assert self.bm.server._send_eio_packet.mock.call_count == 1
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] == "123"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert pkt.encode() == '42/foo,["my event","foo","bar"]'
def test_emit_with_list(self):
sid = _run(self.bm.connect('123', '/foo'))
_run(
self.bm.emit(
'my event', ['foo', 'bar'], namespace='/foo', room=sid
)
)
sid = _run(self.bm.connect("123", "/foo"))
_run(self.bm.emit("my event", ["foo", "bar"], namespace="/foo", room=sid))
assert self.bm.server._send_eio_packet.mock.call_count == 1
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] == "123"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert pkt.encode() == '42/foo,["my event",["foo","bar"]]'
def test_emit_with_none(self):
sid = _run(self.bm.connect('123', '/foo'))
_run(
self.bm.emit(
'my event', None, namespace='/foo', room=sid
)
)
sid = _run(self.bm.connect("123", "/foo"))
_run(self.bm.emit("my event", None, namespace="/foo", room=sid))
assert self.bm.server._send_eio_packet.mock.call_count == 1
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] == "123"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert pkt.encode() == '42/foo,["my event"]'
def test_emit_binary(self):
sid = _run(self.bm.connect('123', '/'))
_run(
self.bm.emit(
u'my event', b'my binary data', namespace='/', room=sid
)
)
sid = _run(self.bm.connect("123", "/"))
_run(self.bm.emit("my event", b"my binary data", namespace="/", room=sid))
assert self.bm.server._send_eio_packet.mock.call_count == 2
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.bm.server._send_eio_packet.mock.call_args_list[0][0][0] == "123"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert pkt.encode() == '451-["my event",{"_placeholder":true,"num":0}]'
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] \
== '123'
assert self.bm.server._send_eio_packet.mock.call_args_list[1][0][0] == "123"
pkt = self.bm.server._send_eio_packet.mock.call_args_list[1][0][1]
assert pkt.encode() == b'my binary data'
assert pkt.encode() == b"my binary data"

296
tests/async/test_namespace.py

@ -6,335 +6,323 @@ from socketio import async_namespace
from .helpers import AsyncMock, _run
@unittest.skipIf(sys.version_info < (3, 5), 'only for Python 3.5+')
@unittest.skipIf(sys.version_info < (3, 5), "only for Python 3.5+")
class TestAsyncNamespace(unittest.TestCase):
def test_connect_event(self):
result = {}
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())
_run(ns.trigger_event('connect', 'sid', {'foo': 'bar'}))
assert result['result'] == ('sid', {'foo': 'bar'})
_run(ns.trigger_event("connect", "sid", {"foo": "bar"}))
assert result["result"] == ("sid", {"foo": "bar"})
def test_disconnect_event(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())
_run(ns.trigger_event('disconnect', 'sid'))
assert result['result'] == 'sid'
_run(ns.trigger_event("disconnect", "sid"))
assert result["result"] == "sid"
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())
_run(ns.trigger_event('custom_message', 'sid', {'data': 'data'}))
assert result['result'] == ('sid', {'data': 'data'})
_run(ns.trigger_event("custom_message", "sid", {"data": "data"}))
assert result["result"] == ("sid", {"data": "data"})
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())
_run(ns.trigger_event('custom_message', 'sid', {'data': 'data'}))
assert result['result'] == ('sid', {'data': 'data'})
_run(ns.trigger_event("custom_message", "sid", {"data": "data"}))
assert result["result"] == ("sid", {"data": "data"})
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())
_run(
ns.trigger_event('another_custom_message', 'sid', {'data': 'data'})
)
_run(ns.trigger_event("another_custom_message", "sid", {"data": "data"}))
assert result == {}
def test_emit(self):
ns = async_namespace.AsyncNamespace('/foo')
ns = async_namespace.AsyncNamespace("/foo")
mock_server = mock.MagicMock()
mock_server.emit = AsyncMock()
ns._set_server(mock_server)
_run(
ns.emit(
'ev', data='data', to='room', skip_sid='skip', callback='cb'
)
)
_run(ns.emit("ev", data="data", to="room", skip_sid="skip", callback="cb"))
ns.server.emit.mock.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,
)
_run(
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.mock.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 = async_namespace.AsyncNamespace('/foo')
ns = async_namespace.AsyncNamespace("/foo")
mock_server = mock.MagicMock()
mock_server.send = AsyncMock()
ns._set_server(mock_server)
_run(ns.send(data='data', to='room', skip_sid='skip', callback='cb'))
_run(ns.send(data="data", to="room", skip_sid="skip", callback="cb"))
ns.server.send.mock.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,
)
_run(
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.mock.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 = async_namespace.AsyncNamespace('/foo')
ns = async_namespace.AsyncNamespace("/foo")
mock_server = mock.MagicMock()
mock_server.call = AsyncMock()
ns._set_server(mock_server)
_run(ns.call('ev', data='data', to='sid'))
_run(ns.call("ev", data="data", to="sid"))
ns.server.call.mock.assert_called_with(
'ev',
data='data',
to='sid',
"ev",
data="data",
to="sid",
sid=None,
namespace='/foo',
namespace="/foo",
timeout=None,
ignore_queue=False,
)
_run(ns.call('ev', data='data', sid='sid', namespace='/bar',
timeout=45, ignore_queue=True))
_run(
ns.call(
"ev",
data="data",
sid="sid",
namespace="/bar",
timeout=45,
ignore_queue=True,
)
)
ns.server.call.mock.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 = async_namespace.AsyncNamespace('/foo')
ns = async_namespace.AsyncNamespace("/foo")
mock_server = mock.MagicMock()
mock_server.enter_room = AsyncMock()
ns._set_server(mock_server)
_run(ns.enter_room('sid', 'room'))
ns.server.enter_room.mock.assert_called_with(
'sid', 'room', namespace='/foo'
)
_run(ns.enter_room('sid', 'room', namespace='/bar'))
ns.server.enter_room.mock.assert_called_with(
'sid', 'room', namespace='/bar'
)
_run(ns.enter_room("sid", "room"))
ns.server.enter_room.mock.assert_called_with("sid", "room", namespace="/foo")
_run(ns.enter_room("sid", "room", namespace="/bar"))
ns.server.enter_room.mock.assert_called_with("sid", "room", namespace="/bar")
def test_leave_room(self):
ns = async_namespace.AsyncNamespace('/foo')
ns = async_namespace.AsyncNamespace("/foo")
mock_server = mock.MagicMock()
mock_server.leave_room = AsyncMock()
ns._set_server(mock_server)
_run(ns.leave_room('sid', 'room'))
ns.server.leave_room.mock.assert_called_with(
'sid', 'room', namespace='/foo'
)
_run(ns.leave_room('sid', 'room', namespace='/bar'))
ns.server.leave_room.mock.assert_called_with(
'sid', 'room', namespace='/bar'
)
_run(ns.leave_room("sid", "room"))
ns.server.leave_room.mock.assert_called_with("sid", "room", namespace="/foo")
_run(ns.leave_room("sid", "room", namespace="/bar"))
ns.server.leave_room.mock.assert_called_with("sid", "room", namespace="/bar")
def test_close_room(self):
ns = async_namespace.AsyncNamespace('/foo')
ns = async_namespace.AsyncNamespace("/foo")
mock_server = mock.MagicMock()
mock_server.close_room = AsyncMock()
ns._set_server(mock_server)
_run(ns.close_room('room'))
ns.server.close_room.mock.assert_called_with('room', namespace='/foo')
_run(ns.close_room('room', namespace='/bar'))
ns.server.close_room.mock.assert_called_with('room', namespace='/bar')
_run(ns.close_room("room"))
ns.server.close_room.mock.assert_called_with("room", namespace="/foo")
_run(ns.close_room("room", namespace="/bar"))
ns.server.close_room.mock.assert_called_with("room", namespace="/bar")
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")
def test_session(self):
ns = async_namespace.AsyncNamespace('/foo')
ns = async_namespace.AsyncNamespace("/foo")
mock_server = mock.MagicMock()
mock_server.get_session = AsyncMock()
mock_server.save_session = AsyncMock()
ns._set_server(mock_server)
_run(ns.get_session('sid'))
ns.server.get_session.mock.assert_called_with('sid', namespace='/foo')
_run(ns.get_session('sid', namespace='/bar'))
ns.server.get_session.mock.assert_called_with('sid', namespace='/bar')
_run(ns.save_session('sid', {'a': 'b'}))
_run(ns.get_session("sid"))
ns.server.get_session.mock.assert_called_with("sid", namespace="/foo")
_run(ns.get_session("sid", namespace="/bar"))
ns.server.get_session.mock.assert_called_with("sid", namespace="/bar")
_run(ns.save_session("sid", {"a": "b"}))
ns.server.save_session.mock.assert_called_with(
'sid', {'a': 'b'}, namespace='/foo'
"sid", {"a": "b"}, namespace="/foo"
)
_run(ns.save_session('sid', {'a': 'b'}, namespace='/bar'))
_run(ns.save_session("sid", {"a": "b"}, namespace="/bar"))
ns.server.save_session.mock.assert_called_with(
'sid', {'a': 'b'}, namespace='/bar'
"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.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 = async_namespace.AsyncNamespace('/foo')
ns = async_namespace.AsyncNamespace("/foo")
mock_server = mock.MagicMock()
mock_server.disconnect = AsyncMock()
ns._set_server(mock_server)
_run(ns.disconnect('sid'))
ns.server.disconnect.mock.assert_called_with('sid', namespace='/foo')
_run(ns.disconnect('sid', namespace='/bar'))
ns.server.disconnect.mock.assert_called_with('sid', namespace='/bar')
_run(ns.disconnect("sid"))
ns.server.disconnect.mock.assert_called_with("sid", namespace="/foo")
_run(ns.disconnect("sid", namespace="/bar"))
ns.server.disconnect.mock.assert_called_with("sid", namespace="/bar")
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())
_run(ns.trigger_event('custom_message', 'sid', {'data': 'data'}))
assert result['result'] == ('sid', {'data': 'data'})
_run(ns.trigger_event("custom_message", "sid", {"data": "data"}))
assert result["result"] == ("sid", {"data": "data"})
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())
_run(ns.trigger_event('custom_message', 'sid', {'data': 'data'}))
assert result['result'] == ('sid', {'data': 'data'})
_run(ns.trigger_event("custom_message", "sid", {"data": "data"}))
assert result["result"] == ("sid", {"data": "data"})
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())
_run(
ns.trigger_event('another_custom_message', 'sid', {'data': 'data'})
)
_run(ns.trigger_event("another_custom_message", "sid", {"data": "data"}))
assert result == {}
def test_emit_client(self):
ns = async_namespace.AsyncClientNamespace('/foo')
ns = async_namespace.AsyncClientNamespace("/foo")
mock_client = mock.MagicMock()
mock_client.emit = AsyncMock()
ns._set_client(mock_client)
_run(ns.emit('ev', data='data', callback='cb'))
_run(ns.emit("ev", data="data", callback="cb"))
ns.client.emit.mock.assert_called_with(
'ev', data='data', namespace='/foo', callback='cb'
"ev", data="data", namespace="/foo", callback="cb"
)
_run(ns.emit('ev', data='data', namespace='/bar', callback='cb'))
_run(ns.emit("ev", data="data", namespace="/bar", callback="cb"))
ns.client.emit.mock.assert_called_with(
'ev', data='data', namespace='/bar', callback='cb'
"ev", data="data", namespace="/bar", callback="cb"
)
def test_send_client(self):
ns = async_namespace.AsyncClientNamespace('/foo')
ns = async_namespace.AsyncClientNamespace("/foo")
mock_client = mock.MagicMock()
mock_client.send = AsyncMock()
ns._set_client(mock_client)
_run(ns.send(data='data', callback='cb'))
ns.client.send.mock.assert_called_with(
'data', namespace='/foo', callback='cb'
)
_run(ns.send(data='data', namespace='/bar', callback='cb'))
ns.client.send.mock.assert_called_with(
'data', namespace='/bar', callback='cb'
)
_run(ns.send(data="data", callback="cb"))
ns.client.send.mock.assert_called_with("data", namespace="/foo", callback="cb")
_run(ns.send(data="data", namespace="/bar", callback="cb"))
ns.client.send.mock.assert_called_with("data", namespace="/bar", callback="cb")
def test_call_client(self):
ns = async_namespace.AsyncClientNamespace('/foo')
ns = async_namespace.AsyncClientNamespace("/foo")
mock_client = mock.MagicMock()
mock_client.call = AsyncMock()
ns._set_client(mock_client)
_run(ns.call('ev', data='data'))
_run(ns.call("ev", data="data"))
ns.client.call.mock.assert_called_with(
'ev', data='data', namespace='/foo', timeout=None
"ev", data="data", namespace="/foo", timeout=None
)
_run(ns.call('ev', data='data', namespace='/bar', timeout=45))
_run(ns.call("ev", data="data", namespace="/bar", timeout=45))
ns.client.call.mock.assert_called_with(
'ev', data='data', namespace='/bar', timeout=45
"ev", data="data", namespace="/bar", timeout=45
)
def test_disconnect_client(self):
ns = async_namespace.AsyncClientNamespace('/foo')
ns = async_namespace.AsyncClientNamespace("/foo")
mock_client = mock.MagicMock()
mock_client.disconnect = AsyncMock()
ns._set_client(mock_client)

555
tests/async/test_pubsub_manager.py

@ -12,7 +12,7 @@ from socketio import packet
from .helpers import AsyncMock, _run
@unittest.skipIf(sys.version_info < (3, 5), 'only for Python 3.5+')
@unittest.skipIf(sys.version_info < (3, 5), "only for Python 3.5+")
class TestAsyncPubSubManager(unittest.TestCase):
def setUp(self):
id = 0
@ -31,18 +31,16 @@ class TestAsyncPubSubManager(unittest.TestCase):
self.pm = async_pubsub_manager.AsyncPubSubManager()
self.pm._publish = AsyncMock()
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 = 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
def test_write_only_init(self):
@ -50,181 +48,198 @@ class TestAsyncPubSubManager(unittest.TestCase):
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
def test_emit(self):
_run(self.pm.emit('foo', 'bar'))
_run(self.pm.emit("foo", "bar"))
self.pm._publish.mock.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_namespace(self):
_run(self.pm.emit('foo', 'bar', namespace='/baz'))
_run(self.pm.emit("foo", "bar", namespace="/baz"))
self.pm._publish.mock.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):
_run(self.pm.emit('foo', 'bar', room='baz'))
_run(self.pm.emit("foo", "bar", room="baz"))
self.pm._publish.mock.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):
_run(self.pm.emit('foo', 'bar', skip_sid='baz'))
_run(self.pm.emit("foo", "bar", skip_sid="baz"))
self.pm._publish.mock.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'
):
_run(self.pm.emit('foo', 'bar', room='baz', callback='cb'))
with mock.patch.object(self.pm, "_generate_ack_id", return_value="123"):
_run(self.pm.emit("foo", "bar", room="baz", callback="cb"))
self.pm._publish.mock.assert_called_once_with(
{
'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 = async_pubsub_manager.AsyncPubSubManager()
with pytest.raises(RuntimeError):
_run(standalone_pm.emit('foo', 'bar', callback='cb'))
_run(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):
_run(self.pm.emit('foo', 'bar', callback='cb'))
_run(self.pm.emit("foo", "bar", callback="cb"))
def test_emit_with_ignore_queue(self):
sid = _run(self.pm.connect('123', '/'))
_run(
self.pm.emit(
'foo', 'bar', room=sid, namespace='/', ignore_queue=True
)
)
sid = _run(self.pm.connect("123", "/"))
_run(self.pm.emit("foo", "bar", room=sid, namespace="/", ignore_queue=True))
self.pm._publish.mock.assert_not_called()
assert self.pm.server._send_eio_packet.mock.call_count == 1
assert self.pm.server._send_eio_packet.mock.call_args_list[0][0][0] \
== '123'
assert self.pm.server._send_eio_packet.mock.call_args_list[0][0][0] == "123"
pkt = self.pm.server._send_eio_packet.mock.call_args_list[0][0][1]
assert pkt.encode() == '42["foo","bar"]'
def test_can_disconnect(self):
sid = _run(self.pm.connect('123', '/'))
assert _run(self.pm.can_disconnect(sid, '/')) is True
_run(self.pm.can_disconnect(sid, '/foo'))
sid = _run(self.pm.connect("123", "/"))
assert _run(self.pm.can_disconnect(sid, "/")) is True
_run(self.pm.can_disconnect(sid, "/foo"))
self.pm._publish.mock.assert_called_once_with(
{'method': 'disconnect', 'sid': sid, 'namespace': '/foo',
'host_id': '123456'}
{
"method": "disconnect",
"sid": sid,
"namespace": "/foo",
"host_id": "123456",
}
)
def test_disconnect(self):
_run(self.pm.disconnect('foo', '/'))
_run(self.pm.disconnect("foo", "/"))
self.pm._publish.mock.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 = _run(self.pm.connect('123', '/'))
self.pm.pre_disconnect(sid, '/')
_run(self.pm.disconnect(sid, '/', ignore_queue=True))
sid = _run(self.pm.connect("123", "/"))
self.pm.pre_disconnect(sid, "/")
_run(self.pm.disconnect(sid, "/", ignore_queue=True))
self.pm._publish.mock.assert_not_called()
assert self.pm.is_connected(sid, '/') is False
assert self.pm.is_connected(sid, "/") is False
def test_enter_room(self):
sid = _run(self.pm.connect('123', '/'))
_run(self.pm.enter_room(sid, '/', 'foo'))
_run(self.pm.enter_room('456', '/', 'foo'))
assert sid in self.pm.rooms['/']['foo']
assert self.pm.rooms['/']['foo'][sid] == '123'
sid = _run(self.pm.connect("123", "/"))
_run(self.pm.enter_room(sid, "/", "foo"))
_run(self.pm.enter_room("456", "/", "foo"))
assert sid in self.pm.rooms["/"]["foo"]
assert self.pm.rooms["/"]["foo"][sid] == "123"
self.pm._publish.mock.assert_called_once_with(
{'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 = _run(self.pm.connect('123', '/'))
_run(self.pm.leave_room(sid, '/', 'foo'))
_run(self.pm.leave_room('456', '/', 'foo'))
assert 'foo' not in self.pm.rooms['/']
sid = _run(self.pm.connect("123", "/"))
_run(self.pm.leave_room(sid, "/", "foo"))
_run(self.pm.leave_room("456", "/", "foo"))
assert "foo" not in self.pm.rooms["/"]
self.pm._publish.mock.assert_called_once_with(
{'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):
_run(self.pm.close_room('foo'))
_run(self.pm.close_room("foo"))
self.pm._publish.mock.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):
_run(self.pm.close_room('foo', '/bar'))
_run(self.pm.close_room("foo", "/bar"))
self.pm._publish.mock.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(
async_manager.AsyncManager, 'emit', new=AsyncMock()
async_manager.AsyncManager, "emit", new=AsyncMock()
) as super_emit:
_run(self.pm._handle_emit({'event': 'foo', 'data': 'bar'}))
_run(self.pm._handle_emit({"event": "foo", "data": "bar"}))
super_emit.mock.assert_called_once_with(
self.pm,
'foo',
'bar',
"foo",
"bar",
namespace=None,
room=None,
skip_sid=None,
@ -233,18 +248,18 @@ class TestAsyncPubSubManager(unittest.TestCase):
def test_handle_emit_with_namespace(self):
with mock.patch.object(
async_manager.AsyncManager, 'emit', new=AsyncMock()
async_manager.AsyncManager, "emit", new=AsyncMock()
) as super_emit:
_run(
self.pm._handle_emit(
{'event': 'foo', 'data': 'bar', 'namespace': '/baz'}
{"event": "foo", "data": "bar", "namespace": "/baz"}
)
)
super_emit.mock.assert_called_once_with(
self.pm,
'foo',
'bar',
namespace='/baz',
"foo",
"bar",
namespace="/baz",
room=None,
skip_sid=None,
callback=None,
@ -252,135 +267,125 @@ class TestAsyncPubSubManager(unittest.TestCase):
def test_handle_emit_with_room(self):
with mock.patch.object(
async_manager.AsyncManager, 'emit', new=AsyncMock()
async_manager.AsyncManager, "emit", new=AsyncMock()
) as super_emit:
_run(
self.pm._handle_emit(
{'event': 'foo', 'data': 'bar', 'room': 'baz'}
)
)
_run(self.pm._handle_emit({"event": "foo", "data": "bar", "room": "baz"}))
super_emit.mock.assert_called_once_with(
self.pm,
'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(
async_manager.AsyncManager, 'emit', new=AsyncMock()
async_manager.AsyncManager, "emit", new=AsyncMock()
) as super_emit:
_run(
self.pm._handle_emit(
{'event': 'foo', 'data': 'bar', 'skip_sid': '123'}
)
self.pm._handle_emit({"event": "foo", "data": "bar", "skip_sid": "123"})
)
super_emit.mock.assert_called_once_with(
self.pm,
'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(
async_manager.AsyncManager, 'emit', new=AsyncMock()
async_manager.AsyncManager, "emit", new=AsyncMock()
) as super_emit:
_run(
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.mock.call_count == 1
assert super_emit.mock.call_args[0] == (self.pm, 'foo', 'bar')
assert super_emit.mock.call_args[1]['namespace'] == '/baz'
assert super_emit.mock.call_args[1]['room'] is None
assert super_emit.mock.call_args[1]['skip_sid'] is None
assert super_emit.mock.call_args[0] == (self.pm, "foo", "bar")
assert super_emit.mock.call_args[1]["namespace"] == "/baz"
assert super_emit.mock.call_args[1]["room"] is None
assert super_emit.mock.call_args[1]["skip_sid"] is None
assert isinstance(
super_emit.mock.call_args[1]['callback'], functools.partial
super_emit.mock.call_args[1]["callback"], functools.partial
)
_run(super_emit.mock.call_args[1]['callback']('one', 2, 'three'))
_run(super_emit.mock.call_args[1]["callback"]("one", 2, "three"))
self.pm._publish.mock.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(
async_manager.AsyncManager, 'emit', new=AsyncMock()
async_manager.AsyncManager, "emit", new=AsyncMock()
) as super_emit:
_run(
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.mock.call_count == 1
assert super_emit.mock.call_args[0] == (self.pm, 'foo', 'bar')
assert super_emit.mock.call_args[1]['namespace'] == '/baz'
assert super_emit.mock.call_args[1]['room'] is None
assert super_emit.mock.call_args[1]['skip_sid'] is None
assert super_emit.mock.call_args[0] == (self.pm, "foo", "bar")
assert super_emit.mock.call_args[1]["namespace"] == "/baz"
assert super_emit.mock.call_args[1]["room"] is None
assert super_emit.mock.call_args[1]["skip_sid"] is None
assert isinstance(
super_emit.mock.call_args[1]['callback'], functools.partial
super_emit.mock.call_args[1]["callback"], functools.partial
)
_run(super_emit.mock.call_args[1]['callback']('one', 2, 'three'))
_run(super_emit.mock.call_args[1]["callback"]("one", 2, "three"))
self.pm._publish.mock.assert_not_called()
def test_handle_callback(self):
host_id = self.pm.host_id
with mock.patch.object(
self.pm, 'trigger_callback', new=AsyncMock()
) as trigger:
with mock.patch.object(self.pm, "trigger_callback", new=AsyncMock()) as trigger:
_run(
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.mock.assert_called_once_with('sid', 123, ('one', 2))
trigger.mock.assert_called_once_with("sid", 123, ("one", 2))
def test_handle_callback_bad_host_id(self):
with mock.patch.object(
self.pm, 'trigger_callback', new=AsyncMock()
) as trigger:
with mock.patch.object(self.pm, "trigger_callback", new=AsyncMock()) as trigger:
_run(
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),
}
)
)
@ -388,122 +393,124 @@ class TestAsyncPubSubManager(unittest.TestCase):
def test_handle_callback_missing_args(self):
host_id = self.pm.host_id
with mock.patch.object(
self.pm, 'trigger_callback', new=AsyncMock()
) as trigger:
with mock.patch.object(self.pm, "trigger_callback", new=AsyncMock()) as trigger:
_run(
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,
}
)
)
_run(
self.pm._handle_callback(
{
'method': 'callback',
'host_id': host_id,
'sid': 'sid',
'namespace': '/',
"method": "callback",
"host_id": host_id,
"sid": "sid",
"namespace": "/",
}
)
)
_run(
self.pm._handle_callback(
{'method': 'callback', 'host_id': host_id, 'sid': 'sid'}
)
)
_run(
self.pm._handle_callback(
{'method': 'callback', 'host_id': host_id}
{"method": "callback", "host_id": host_id, "sid": "sid"}
)
)
_run(self.pm._handle_callback({"method": "callback", "host_id": host_id}))
assert trigger.mock.call_count == 0
def test_handle_disconnect(self):
_run(
self.pm._handle_disconnect(
{'method': 'disconnect', 'sid': '123', 'namespace': '/foo'}
{"method": "disconnect", "sid": "123", "namespace": "/foo"}
)
)
self.pm.server.disconnect.mock.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 = _run(self.pm.connect('123', '/'))
sid = _run(self.pm.connect("123", "/"))
with mock.patch.object(
async_manager.AsyncManager, 'enter_room', new=AsyncMock()
async_manager.AsyncManager, "enter_room", new=AsyncMock()
) as super_enter_room:
_run(
self.pm._handle_enter_room(
{'method': 'enter_room', 'sid': sid, 'namespace': '/',
'room': 'foo'}
{
"method": "enter_room",
"sid": sid,
"namespace": "/",
"room": "foo",
}
)
)
_run(
self.pm._handle_enter_room(
{'method': 'enter_room', 'sid': '456', 'namespace': '/',
'room': 'foo'}
{
"method": "enter_room",
"sid": "456",
"namespace": "/",
"room": "foo",
}
)
)
super_enter_room.mock.assert_called_once_with(
self.pm, sid, '/', 'foo'
)
super_enter_room.mock.assert_called_once_with(self.pm, sid, "/", "foo")
def test_handle_leave_room(self):
sid = _run(self.pm.connect('123', '/'))
sid = _run(self.pm.connect("123", "/"))
with mock.patch.object(
async_manager.AsyncManager, 'leave_room', new=AsyncMock()
async_manager.AsyncManager, "leave_room", new=AsyncMock()
) as super_leave_room:
_run(
self.pm._handle_leave_room(
{'method': 'leave_room', 'sid': sid, 'namespace': '/',
'room': 'foo'}
{
"method": "leave_room",
"sid": sid,
"namespace": "/",
"room": "foo",
}
)
)
_run(
self.pm._handle_leave_room(
{'method': 'leave_room', 'sid': '456', 'namespace': '/',
'room': 'foo'}
{
"method": "leave_room",
"sid": "456",
"namespace": "/",
"room": "foo",
}
)
)
super_leave_room.mock.assert_called_once_with(
self.pm, sid, '/', 'foo'
)
super_leave_room.mock.assert_called_once_with(self.pm, sid, "/", "foo")
def test_handle_close_room(self):
with mock.patch.object(
async_manager.AsyncManager, 'close_room', new=AsyncMock()
async_manager.AsyncManager, "close_room", new=AsyncMock()
) as super_close_room:
_run(
self.pm._handle_close_room(
{'method': 'close_room', 'room': 'foo'}
)
)
_run(self.pm._handle_close_room({"method": "close_room", "room": "foo"}))
super_close_room.mock.assert_called_once_with(
self.pm, room='foo', namespace=None
self.pm, room="foo", namespace=None
)
def test_handle_close_room_with_namespace(self):
with mock.patch.object(
async_manager.AsyncManager, 'close_room', new=AsyncMock()
async_manager.AsyncManager, "close_room", new=AsyncMock()
) as super_close_room:
_run(
self.pm._handle_close_room(
{
'method': 'close_room',
'room': 'foo',
'namespace': '/bar',
"method": "close_room",
"room": "foo",
"namespace": "/bar",
}
)
)
super_close_room.mock.assert_called_once_with(
self.pm, room='foo', namespace='/bar'
self.pm, room="foo", namespace="/bar"
)
def test_background_thread(self):
@ -518,73 +525,101 @@ class TestAsyncPubSubManager(unittest.TestCase):
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}
)
raise asyncio.CancelledError() # force the thread to exit
self.pm._listen = messages
_run(self.pm._thread())
self.pm._handle_emit.mock.assert_called_once_with(
{'method': 'emit', 'value': 'foo', 'host_id': 'x'}
{"method": "emit", "value": "foo", "host_id": "x"}
)
self.pm._handle_callback.mock.assert_any_call(
{'method': 'callback', 'value': 'bar', 'host_id': 'x'}
{"method": "callback", "value": "bar", "host_id": "x"}
)
self.pm._handle_callback.mock.assert_any_call(
{'method': 'callback', 'value': 'bar', 'host_id': host_id}
{"method": "callback", "value": "bar", "host_id": host_id}
)
self.pm._handle_disconnect.mock.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.mock.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.mock.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.mock.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 = AsyncMock(side_effect=[ValueError(),
asyncio.CancelledError])
self.pm._handle_emit = 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
_run(self.pm._thread())
self.pm._handle_emit.mock.assert_any_call(
{'method': 'emit', 'value': 'foo', 'host_id': 'x'}
{"method": "emit", "value": "foo", "host_id": "x"}
)
self.pm._handle_emit.mock.assert_called_with(
{'method': 'emit', 'value': 'bar', 'host_id': 'x'}
{"method": "emit", "value": "bar", "host_id": "x"}
)

899
tests/async/test_server.py

File diff suppressed because it is too large

132
tests/async/test_simple_client.py

@ -10,91 +10,115 @@ from .helpers import AsyncMock, _run
class TestAsyncAsyncSimpleClient(unittest.TestCase):
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
def test_connect(self):
client = AsyncSimpleClient(123, a='b')
with mock.patch('socketio.async_simple_client.AsyncClient') \
as mock_client:
client = AsyncSimpleClient(123, a="b")
with mock.patch("socketio.async_simple_client.AsyncClient") as mock_client:
mock_client.return_value.connect = AsyncMock()
_run(client.connect('url', headers='h', auth='a', transports='t',
namespace='n', socketio_path='s',
wait_timeout='w'))
mock_client.assert_called_once_with(123, a='b')
_run(
client.connect(
"url",
headers="h",
auth="a",
transports="t",
namespace="n",
socketio_path="s",
wait_timeout="w",
)
)
mock_client.assert_called_once_with(123, a="b")
assert client.client == mock_client()
mock_client().connect.mock.assert_called_once_with(
'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()
def test_connect_context_manager(self):
async def _t():
async with AsyncSimpleClient(123, a='b') as client:
with mock.patch('socketio.async_simple_client.AsyncClient') \
as mock_client:
async with AsyncSimpleClient(123, a="b") as client:
with mock.patch(
"socketio.async_simple_client.AsyncClient"
) as mock_client:
mock_client.return_value.connect = AsyncMock()
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.mock.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()
_run(_t())
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):
_run(client.connect('url'))
_run(client.connect("url"))
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"
def test_emit(self):
client = AsyncSimpleClient()
client.client = mock.MagicMock()
client.client.emit = AsyncMock()
client.namespace = '/ns'
client.namespace = "/ns"
client.connected_event.set()
client.connected = True
_run(client.emit('foo', 'bar'))
client.client.emit.mock.assert_called_once_with('foo', 'bar',
namespace='/ns')
_run(client.emit("foo", "bar"))
client.client.emit.mock.assert_called_once_with("foo", "bar", namespace="/ns")
def test_emit_disconnected(self):
client = AsyncSimpleClient()
client.connected_event.set()
client.connected = False
with pytest.raises(DisconnectedError):
_run(client.emit('foo', 'bar'))
_run(client.emit("foo", "bar"))
def test_emit_retries(self):
client = AsyncSimpleClient()
@ -104,28 +128,29 @@ class TestAsyncAsyncSimpleClient(unittest.TestCase):
client.client.emit = AsyncMock()
client.client.emit.mock.side_effect = [SocketIOError(), None]
_run(client.emit('foo', 'bar'))
client.client.emit.mock.assert_called_with('foo', 'bar', namespace='/')
_run(client.emit("foo", "bar"))
client.client.emit.mock.assert_called_with("foo", "bar", namespace="/")
def test_call(self):
client = AsyncSimpleClient()
client.client = mock.MagicMock()
client.client.call = AsyncMock()
client.client.call.mock.return_value = 'result'
client.namespace = '/ns'
client.client.call.mock.return_value = "result"
client.namespace = "/ns"
client.connected_event.set()
client.connected = True
assert _run(client.call('foo', 'bar')) == 'result'
assert _run(client.call("foo", "bar")) == "result"
client.client.call.mock.assert_called_once_with(
'foo', 'bar', namespace='/ns', timeout=60)
"foo", "bar", namespace="/ns", timeout=60
)
def test_call_disconnected(self):
client = AsyncSimpleClient()
client.connected_event.set()
client.connected = False
with pytest.raises(DisconnectedError):
_run(client.call('foo', 'bar'))
_run(client.call("foo", "bar"))
def test_call_retries(self):
client = AsyncSimpleClient()
@ -133,17 +158,18 @@ class TestAsyncAsyncSimpleClient(unittest.TestCase):
client.connected = True
client.client = mock.MagicMock()
client.client.call = AsyncMock()
client.client.call.mock.side_effect = [SocketIOError(), 'result']
client.client.call.mock.side_effect = [SocketIOError(), "result"]
assert _run(client.call('foo', 'bar')) == 'result'
client.client.call.mock.assert_called_with('foo', 'bar', namespace='/',
timeout=60)
assert _run(client.call("foo", "bar")) == "result"
client.client.call.mock.assert_called_with(
"foo", "bar", namespace="/", timeout=60
)
def test_receive_with_input_buffer(self):
client = AsyncSimpleClient()
client.input_buffer = ['foo', 'bar']
assert _run(client.receive()) == 'foo'
assert _run(client.receive()) == 'bar'
client.input_buffer = ["foo", "bar"]
assert _run(client.receive()) == "foo"
assert _run(client.receive()) == "bar"
def test_receive_without_input_buffer(self):
client = AsyncSimpleClient()
@ -152,11 +178,11 @@ class TestAsyncAsyncSimpleClient(unittest.TestCase):
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 _run(client.receive()) == 'foo'
assert _run(client.receive()) == "foo"
def test_receive_with_timeout(self):
client = AsyncSimpleClient()

20
tests/asyncio_web_server.py

@ -13,16 +13,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 +47,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)

247
tests/common/test_admin.py

@ -16,10 +16,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):
@ -29,12 +30,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
instrumented_server = sio.instrument(auth=auth, **ikwargs)
server = SocketIOWebServer(sio)
@ -61,89 +62,96 @@ 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(unittest.TestCase):
def setUp(self):
print('threads at start:', threading.enumerate())
print("threads at start:", threading.enumerate())
self.thread_count = threading.active_count()
def tearDown(self):
print('threads at end:', threading.enumerate())
print("threads at end:", threading.enumerate())
assert self.thread_count == threading.active_count()
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, isvr):
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, isvr):
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, isvr):
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, isvr):
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, isvr):
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
expected = ['config', 'all_sockets', 'server_stats']
expected = ["config", "all_sockets", "server_stats"]
events = {}
while expected:
data = admin_client.receive(timeout=5)
@ -151,46 +159,45 @@ class TestAdmin(unittest.TestCase):
events[data[0]] = data[1]
expected.remove(data[0])
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']
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, isvr):
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 = isvr._check_for_upgrade
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
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
expected = ['config', 'all_sockets', 'server_stats']
expected = ["config", "all_sockets", "server_stats"]
events = {}
while expected:
data = admin_client.receive(timeout=5)
@ -198,36 +205,37 @@ class TestAdmin(unittest.TestCase):
events[data[0]] = data[1]
expected.remove(data[0])
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)
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, isvr):
with socketio.SimpleClient() as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin')
expected = ['config', 'server_stats']
admin_client.connect("http://localhost:8900", namespace="/admin")
expected = ["config", "server_stats"]
events = {}
while expected:
data = admin_client.receive(timeout=5)
@ -235,50 +243,49 @@ class TestAdmin(unittest.TestCase):
events[data[0]] = data[1]
expected.remove(data[0])
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']
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, isvr):
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(
'emit', ('/', 'room', 'foo', {'bar': 'baz'}))
admin_client.emit("join", ("/", "room", client1.sid))
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

798
tests/common/test_client.py

File diff suppressed because it is too large

391
tests/common/test_manager.py

@ -24,329 +24,324 @@ class TestBaseManager(unittest.TestCase):
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')
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', room=sid)
sid = self.bm.connect("123", "/foo")
self.bm.connect("456", "/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_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(u'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"

18
tests/common/test_middleware.py

@ -7,33 +7,31 @@ from socketio import middleware
class TestMiddleware(unittest.TestCase):
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")]
)

14
tests/common/test_msgpack_packet.py

@ -7,7 +7,8 @@ from socketio import packet
class TestMsgPackPacket(unittest.TestCase):
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
@ -16,7 +17,8 @@ class TestMsgPackPacket(unittest.TestCase):
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
@ -24,13 +26,13 @@ class TestMsgPackPacket(unittest.TestCase):
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"}

263
tests/common/test_namespace.py

@ -10,262 +10,247 @@ class TestNamespace(unittest.TestCase):
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):
result['result'] = sid
result["result"] = sid
ns = MyNamespace('/foo')
ns = MyNamespace("/foo")
ns._set_server(mock.MagicMock())
ns.trigger_event('disconnect', 'sid')
assert result['result'] == 'sid'
ns.trigger_event("disconnect", "sid")
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_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()

162
tests/common/test_packet.py

@ -13,130 +13,124 @@ class TestPacket(unittest.TestCase):
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"]'
@ -146,9 +140,7 @@ class TestPacket(unittest.TestCase):
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"]'
@ -158,66 +150,66 @@ class TestPacket(unittest.TestCase):
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(
@ -226,12 +218,12 @@ class TestPacket(unittest.TestCase):
'"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(
@ -240,12 +232,12 @@ class TestPacket(unittest.TestCase):
'"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(
@ -254,14 +246,14 @@ class TestPacket(unittest.TestCase):
'"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-"}')
@ -270,14 +262,14 @@ class TestPacket(unittest.TestCase):
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"})

539
tests/common/test_pubsub_manager.py

@ -25,18 +25,16 @@ class TestPubSubManager(unittest.TestCase):
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):
@ -44,190 +42,210 @@ class TestPubSubManager(unittest.TestCase):
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_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,
@ -235,210 +253,186 @@ class TestPubSubManager(unittest.TestCase):
)
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()
@ -452,29 +446,47 @@ class TestPubSubManager(unittest.TestCase):
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:
@ -483,36 +495,45 @@ class TestPubSubManager(unittest.TestCase):
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:
@ -521,8 +542,8 @@ class TestPubSubManager(unittest.TestCase):
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"}
)

867
tests/common/test_server.py

File diff suppressed because it is too large

124
tests/common/test_simple_client.py

@ -7,79 +7,103 @@ from socketio.exceptions import SocketIOError, TimeoutError, DisconnectedError
class TestSimpleClient(unittest.TestCase):
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
def test_connect(self):
client = SimpleClient(123, a='b')
with mock.patch('socketio.simple_client.Client') as mock_client:
client.connect('url', headers='h', auth='a', transports='t',
namespace='n', socketio_path='s', wait_timeout='w')
mock_client.assert_called_once_with(123, a='b')
client = SimpleClient(123, a="b")
with mock.patch("socketio.simple_client.Client") as mock_client:
client.connect(
"url",
headers="h",
auth="a",
transports="t",
namespace="n",
socketio_path="s",
wait_timeout="w",
)
mock_client.assert_called_once_with(123, a="b")
assert client.client == mock_client()
mock_client().connect.assert_called_once_with(
'url', headers='h', auth='a', transports='t',
namespaces=['n'], socketio_path='s', wait_timeout='w')
"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()
def test_connect_context_manager(self):
with SimpleClient(123, a='b') as client:
with mock.patch('socketio.simple_client.Client') as mock_client:
client.connect('url', headers='h', auth='a', transports='t',
namespace='n', socketio_path='s',
wait_timeout='w')
mock_client.assert_called_once_with(123, a='b')
with SimpleClient(123, a="b") as client:
with mock.patch("socketio.simple_client.Client") as mock_client:
client.connect(
"url",
headers="h",
auth="a",
transports="t",
namespace="n",
socketio_path="s",
wait_timeout="w",
)
mock_client.assert_called_once_with(123, a="b")
assert client.client == mock_client()
mock_client().connect.assert_called_once_with(
'url', headers='h', auth='a', transports='t',
namespaces=['n'], socketio_path='s', wait_timeout='w')
"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()
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()
@ -88,44 +112,44 @@ class TestSimpleClient(unittest.TestCase):
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()
@ -134,11 +158,11 @@ class TestSimpleClient(unittest.TestCase):
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()

6
tests/performance/binary_packet.py

@ -3,7 +3,7 @@ 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 +17,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.")

6
tests/performance/json_packet.py

@ -3,7 +3,7 @@ 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 +14,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.")

6
tests/performance/namespace_packet.py

@ -3,7 +3,7 @@ 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 +14,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.")

10
tests/performance/server_receive.py

@ -6,16 +6,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.")

10
tests/performance/server_send.py

@ -14,16 +14,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.")

10
tests/performance/server_send_broadcast.py

@ -15,16 +15,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.")

6
tests/performance/text_packet.py

@ -3,7 +3,7 @@ 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 +14,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.")

21
tests/web_server.py

@ -15,13 +15,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 +36,7 @@ class SocketIOWebServer:
The server is started in a background thread.
"""
class ThreadingWSGIServer(ThreadingMixIn, WSGIServer):
pass
@ -44,20 +46,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 +80,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 []

Loading…
Cancel
Save