python-socketio review
python-socketio implements both ends of the Socket.IO protocol in Python. Servers can use synchronous WSGI integrations or asyncio with ASGI, aiohttp, Tornado, and other documented hosts; clients get matching sync and async classes. Its event layer supplies acknowledgements, rooms, namespaces, reconnect handling, connection sessions, polling fallback, and WebSocket transport over python-engineio. Version 5.16.4 removes an incomplete binary packet when a client disconnects mid-transfer. This is Socket.IO protocol revision 5 over Engine.IO revision 4, so ordinary WebSocket clients cannot talk to it directly. Our import test succeeded, but the installed distribution did not advertise inline typing through py.typed.
Choose python-socketio because a compatible Socket.IO peer or its event features are a real requirement. Walk away for plain WebSocket, durable delivery, package-owned typing, or horizontal scaling without a queue and transport plan.
We installed it
| Install | ✓ · 0.3s | 6 packages on disk · 2 MB |
| Import | ✓ | import socketio in 0.56s · pure Python · requires Python >=3.8 |
| Known vulns | 0 | (pip-audit) |
Answers from our run
Does python-socketio install cleanly?
Yes. In a fresh container with an empty cache, pip install python-socketio finished in 0.3s, leaving 6 packages and 2 MB on disk. pip-audit reported no known vulnerabilities.
What does python-socketio need to run?
Python >=3.8, and nothing compiled: it is pure Python. In our run import socketio succeeded in 0.56s.
python-socketio or websockets: which should you use?
websockets: Use it when both peers speak plain WebSocket and an asyncio protocol library is enough without Socket.IO packet semantics. Choose python-socketio because a compatible Socket.IO peer or its event features are a real requirement.
When should you not use python-socketio?
The other side speaks RFC WebSocket only. Socket.IO adds its own Engine.IO handshake, packet types, and event framing, so the protocols are not interchangeable
Use it if
- A browser, Node, mobile, or Python peer already uses Socket.IO 3.x or 4.x and the backend needs a compatible Python endpoint
- Named events, acknowledgement callbacks, rooms, namespaces, reconnects, and HTTP polling fallback save application code you would otherwise own
- You need a client as well as a server, with a choice between synchronous handlers and native asyncio handlers
- A Redis, RabbitMQ, Kafka, or other documented client manager can coordinate emits across your worker processes
- The other side speaks RFC WebSocket only. Socket.IO adds its own Engine.IO handshake, packet types, and event framing, so the protocols are not interchangeable
- Messages must survive disconnects or be replayed later. Rooms, emits, and acknowledgements operate on live connections and do not replace a durable broker
- You only send server-to-browser notifications. Server-Sent Events can remove the bidirectional protocol, reconnection state, and WebSocket proxy configuration
- You expect several Gunicorn workers to work without design changes. The deployment guide requires WebSocket-only clients plus a shared message queue because long polling does not fit Gunicorn's worker routing
- Your organization requires package-owned type information. Our measured 5.16.4 install had no py.typed marker despite exposing a large sync and async API
- You are starting an eventlet-based service. The official server guide says eventlet is in maintenance mode and does not recommend it for new work
Setup reality
python-socketio 5.16.4 installed in 0.3 seconds in our fresh Python 3.12 container. The resulting environment had 6 packages and occupied 2 MB. The package declared 8 direct dependencies, required Python 3.8 or newer, and contained pure Python. import socketio completed in 0.56 seconds. pip-audit reported no known vulnerabilities. We found no py.typed marker, so type-checker behavior depends on external stubs or local annotations rather than package-owned inline types.
The base server install is only one layer. A synchronous client needs the client extra for requests and websocket-client; an asyncio client needs asyncio-client for aiohttp. Servers still need a WSGI or ASGI host, and message-queue managers bring their own Redis, RabbitMQ, Kafka, or other client packages. Match protocols before chasing proxy bugs: python-socketio 5.x pairs with JavaScript Socket.IO 3.x and 4.x, Socket.IO protocol 5, and Engine.IO protocol 4.
Choose Server plus WSGIApp for threaded WSGI, or AsyncServer plus ASGIApp for asyncio. Async event handlers must await emits, session calls, and room operations. The connect handler receives request data and the client's auth payload, which your application must verify before accepting the session. Same-origin checks are enabled. Set a narrow cors_allowed_origins list for a separate frontend instead of allowing every origin, and configure the reverse proxy to preserve WebSocket upgrades and long-lived connections.
Connection sessions vanish at disconnect, including an unexpected disconnect followed by reconnect. Keep login state and durable subscriptions elsewhere. Multiple server processes need a shared client manager so an emit in one process reaches clients owned by another. Gunicorn multi-worker deployments must also force WebSocket because its load balancing cannot keep Engine.IO long-poll requests on one worker. Acknowledgements can time out and only confirm the client handler replied; they do not make a business event durable.
Patterns
Mount an asyncio Socket.IO server create-asgi-app
import socketio
sio = socketio.AsyncServer(
async_mode="asgi",
cors_allowed_origins=["https://app.example.com"],
)
app = socketio.ASGIApp(sio)Run app with an ASGI server. List the browser origins you own instead of using a wildcard origin policy.
Put Socket.IO in front of FastAPI wrap-existing-asgi-app
import socketio
from my_api import api
sio = socketio.AsyncServer(async_mode="asgi")
app = socketio.ASGIApp(sio, api)ASGIApp handles the Socket.IO path and forwards other traffic to api. Confirm proxy routing sends both polling and WebSocket requests to this wrapper.
Reject an invalid connection authenticate-a-client
from socketio.exceptions import ConnectionRefusedError
@sio.event
async def connect(sid, environ, auth):
token = (auth or {}).get("token")
account = await verify_token(token)
if account is None:
raise ConnectionRefusedError("authentication failed")
await sio.save_session(sid, {"account_id": account.id})The auth object comes from the client and must be verified. The saved Socket.IO session disappears when this connection ends.
Return an acknowledgement payload handle-and-ack-event
@sio.event
async def rename_project(sid, data):
session = await sio.get_session(sid)
name = validate_name(data["name"])
await rename_for_account(session["account_id"], data["project_id"], name)
return {"accepted": True}Returning data replies only when the sender requested an acknowledgement. Validate event payloads because python-socketio does not impose an application schema.
Address a single connection emit-to-one-client
await sio.emit(
"report_ready",
{"report_id": report_id},
to=recipient_sid,
)A sid identifies one current connection. Maintain your own account-to-connection mapping if a user can open several tabs or devices.
Call a client with a timeout wait-for-client-ack
from socketio.exceptions import TimeoutError
try:
reply = await sio.call(
"confirm_export",
{"export_id": export_id},
to=recipient_sid,
timeout=10,
)
except TimeoutError:
reply = Nonecall targets one client and waits for its acknowledgement. A reply does not prove that later work was stored or completed.
Authorize a room before joining join-and-emit-room
@sio.event
async def join_project(sid, data):
project_id = data["project_id"]
session = await sio.get_session(sid)
await require_project_access(session["account_id"], project_id)
await sio.enter_room(sid, f"project:{project_id}")
await sio.emit("member_joined", {"sid": sid}, room=f"project:{project_id}", skip_sid=sid)A room name groups connections; it does not enforce permission. Perform authorization before enter_room and before sensitive room emits.
Mutate temporary session data update-connection-session
@sio.event
async def choose_workspace(sid, data):
async with sio.session(sid) as session:
session["workspace_id"] = data["workspace_id"]The context manager saves changes for this connection and namespace. Reconnect creates a new session, so persistent preferences belong in a database.
Share clients across ASGI workers coordinate-with-redis
import os
import socketio
manager = socketio.AsyncRedisManager(os.environ["REDIS_URL"])
sio = socketio.AsyncServer(
async_mode="asgi",
client_manager=manager,
transports=["websocket"],
)
app = socketio.ASGIApp(sio)Install the Redis client separately and give every worker the same queue configuration. WebSocket-only mode removes long-polling fallback.
Publish from a non-server process emit-from-job-worker
import os
import socketio
manager = socketio.RedisManager(os.environ["REDIS_URL"], write_only=True)
manager.emit(
"job_finished",
{"job_id": job_id},
room=f"account:{account_id}",
)The external process and Socket.IO servers need the same manager channel. Pub/sub delivery is live fan-out, not a stored job result.
Use the synchronous Python client connect-sync-client
import socketio
client = socketio.Client(reconnection=True)
client.connect(
"https://realtime.example.com",
auth={"token": token},
)
client.emit("subscribe", {"project_id": "42"})
client.wait()Install the client extra. Leaving transports unset permits Engine.IO to start with polling and upgrade when the server and proxy support WebSocket.
Handle events with AsyncClient connect-async-client
import socketio
client = socketio.AsyncClient()
@client.on("report_ready")
async def on_report_ready(data):
await fetch_report(data["report_id"])
await client.connect("https://realtime.example.com", auth={"token": token})
await client.wait()Install the asyncio-client extra. Keep blocking file or network calls out of the async event handler.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| websockets | PyPI | Use it when both peers speak plain WebSocket and an asyncio protocol library is enough without Socket.IO packet semantics |
| Flask-SocketIO | PyPI | Use it for a Flask application that wants framework-specific session, handler, and deployment integration around python-socketio |
| channels | PyPI | Use it when Django, ASGI consumers, channel layers, and Django authentication should define the realtime architecture |
| aiohttp | PyPI | Use it when an aiohttp service only needs its native HTTP and WebSocket primitives and controls both ends of the wire format |
More web backend guides
urllib3 · requests · ws · anyio · httpx · undici · the whole shelf →
How this guide is made: grounded in the library's documentation, release notes, changelog, and issue history, on a fixed rubric — not a hands-on install of every release. The 50 most-downloaded entries are additionally install-verified in clean containers. Corrections: contact the desk.

