Launch Week Day 2: Introducing Reports: An Extensible Reporting Framework for Socket Data.Learn More
Socket
Book a DemoSign in
Socket

python-socketio

Package Overview
Dependencies
Maintainers
1
Versions
107
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

python-socketio - pypi Package Compare versions

Comparing version
5.13.0
to
5.14.0
+19
-0
docs/server.rst

@@ -1092,2 +1092,21 @@ The Socket.IO Server

Deploying the Message Queue for Production
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
For a production deployment there are a few recommendations to keep your
application secure.
First of all, the message queue should never be listening on a public network
interface, to ensure that external clients never connect to it. For a single
node deployment, the queue should only listen on `localhost`. For a multi-node
system the use of a private network (VPC), where the communication between
servers can happen privately is highly recommended.
In addition, all message queues support authentication and encryption, which
can strenthen the security of the deployment. Authentication ensures that only
the Socket.IO servers and related processes have access, while encryption
prevents data from being collected by a third-party that is listening on the
network. Access credentials can be included in the connection URLs that are
passed to the client managers.
Horizontal Scaling

@@ -1094,0 +1113,0 @@ ~~~~~~~~~~~~~~~~~~

+2
-2
Metadata-Version: 2.4
Name: python-socketio
Version: 5.13.0
Version: 5.14.0
Summary: Socket.IO server and client for Python
Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/miguelgrinberg/python-socketio

@@ -11,3 +12,2 @@ Project-URL: Bug Tracker, https://github.com/miguelgrinberg/python-socketio/issues

Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent

@@ -14,0 +14,0 @@ Requires-Python: >=3.8

[project]
name = "python-socketio"
version = "5.13.0"
version = "5.14.0"
license = {text = "MIT"}
authors = [

@@ -12,3 +13,2 @@ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" },

"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",

@@ -15,0 +15,0 @@ ]

Metadata-Version: 2.4
Name: python-socketio
Version: 5.13.0
Version: 5.14.0
Summary: Socket.IO server and client for Python
Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/miguelgrinberg/python-socketio

@@ -11,3 +12,2 @@ Project-URL: Bug Tracker, https://github.com/miguelgrinberg/python-socketio/issues

Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent

@@ -14,0 +14,0 @@ Requires-Python: >=3.8

import asyncio
import pickle
from engineio import json
from .async_pubsub_manager import AsyncPubSubManager

@@ -46,2 +46,3 @@

'virtualenv).')
super().__init__(channel=channel, write_only=write_only, logger=logger)
self.url = url

@@ -52,3 +53,2 @@ self._lock = asyncio.Lock()

self.publisher_exchange = None
super().__init__(channel=channel, write_only=write_only, logger=logger)

@@ -87,3 +87,3 @@ async def _connection(self):

aio_pika.Message(
body=pickle.dumps(data),
body=json.dumps(data),
delivery_mode=aio_pika.DeliveryMode.PERSISTENT

@@ -119,3 +119,3 @@ ), routing_key='*',

async with message.process():
yield pickle.loads(message.body)
yield message.body
retry_sleep = 1

@@ -122,0 +122,0 @@ except aio_pika.AMQPException:

@@ -142,2 +142,3 @@ import asyncio

self.namespaces = {}
self.failed_namespaces = []
if self._connect_event is None:

@@ -170,3 +171,4 @@ self._connect_event = self.eio.create_event()

self._connect_event.clear()
if set(self.namespaces) == set(self.connection_namespaces):
if len(self.namespaces) + len(self.failed_namespaces) == \
len(self.connection_namespaces):
break

@@ -178,3 +180,4 @@ except asyncio.TimeoutError:

raise exceptions.ConnectionError(
'One or more namespaces failed to connect')
'One or more namespaces failed to connect'
', '.join(self.failed_namespaces))

@@ -197,3 +200,2 @@ self.connected = True

# connected while sleeping above
print('oops')
continue

@@ -412,3 +414,3 @@ break

self.connected = False
await self.eio.disconnect(abort=True)
await self.eio.disconnect()

@@ -457,2 +459,3 @@ async def _handle_event(self, namespace, id, data):

await self._trigger_event('connect_error', namespace, *data)
self.failed_namespaces.append(namespace)
self._connect_event.set()

@@ -459,0 +462,0 @@ if namespace in self.namespaces:

@@ -6,3 +6,2 @@ import asyncio

from engineio import json
import pickle

@@ -206,12 +205,6 @@ from .async_manager import AsyncManager

else:
if isinstance(message, bytes): # pragma: no cover
try:
data = pickle.loads(message)
except:
pass
if data is None:
try:
data = json.loads(message)
except:
pass
try:
data = json.loads(message)
except:
pass
if data and 'method' in data:

@@ -218,0 +211,0 @@ self._get_logger().debug('pubsub message: {}'.format(

import asyncio
import pickle
from urllib.parse import urlparse

@@ -15,2 +15,10 @@ try: # pragma: no cover

try: # pragma: no cover
from valkey import asyncio as valkey
from valkey.exceptions import ValkeyError
except ImportError: # pragma: no cover
valkey = None
ValkeyError = None
from engineio import json
from .async_pubsub_manager import AsyncPubSubManager

@@ -51,17 +59,36 @@ from .redis_manager import parse_redis_sentinel_url

write_only=False, logger=None, redis_options=None):
if aioredis is None:
if aioredis is None and valkey is None:
raise RuntimeError('Redis package is not installed '
'(Run "pip install redis" in your virtualenv).')
if not hasattr(aioredis.Redis, 'from_url'):
'(Run "pip install redis" or '
'"pip install valkey" '
'in your virtualenv).')
if aioredis and not hasattr(aioredis.Redis, 'from_url'):
raise RuntimeError('Version 2 of aioredis package is required.')
super().__init__(channel=channel, write_only=write_only, logger=logger)
self.redis_url = url
self.redis_options = redis_options or {}
self._redis_connect()
super().__init__(channel=channel, write_only=write_only, logger=logger)
def _get_redis_module_and_error(self):
parsed_url = urlparse(self.redis_url)
schema = parsed_url.scheme.split('+', 1)[0].lower()
if schema == 'redis':
if aioredis is None or RedisError is None:
raise RuntimeError('Redis package is not installed '
'(Run "pip install redis" '
'in your virtualenv).')
return aioredis, RedisError
if schema == 'valkey':
if valkey is None or ValkeyError is None:
raise RuntimeError('Valkey package is not installed '
'(Run "pip install valkey" '
'in your virtualenv).')
return valkey, ValkeyError
error_msg = f'Unsupported Redis URL schema: {schema}'
raise ValueError(error_msg)
def _redis_connect(self):
if not self.redis_url.startswith('redis+sentinel://'):
self.redis = aioredis.Redis.from_url(self.redis_url,
**self.redis_options)
else:
module, _ = self._get_redis_module_and_error()
parsed_url = urlparse(self.redis_url)
if parsed_url.scheme in {"redis+sentinel", "valkey+sentinel"}:
sentinels, service_name, connection_kwargs = \

@@ -71,4 +98,7 @@ parse_redis_sentinel_url(self.redis_url)

kwargs.update(connection_kwargs)
sentinel = aioredis.sentinel.Sentinel(sentinels, **kwargs)
sentinel = module.sentinel.Sentinel(sentinels, **kwargs)
self.redis = sentinel.master_for(service_name or self.channel)
else:
self.redis = module.Redis.from_url(self.redis_url,
**self.redis_options)
self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)

@@ -78,2 +108,3 @@

retry = True
_, error = self._get_redis_module_and_error()
while True:

@@ -84,11 +115,16 @@ try:

return await self.redis.publish(
self.channel, pickle.dumps(data))
except RedisError:
self.channel, json.dumps(data))
except error as exc:
if retry:
self._get_logger().error('Cannot publish to redis... '
'retrying')
self._get_logger().error(
'Cannot publish to redis... '
'retrying',
extra={"redis_exception": str(exc)})
retry = False
else:
self._get_logger().error('Cannot publish to redis... '
'giving up')
self._get_logger().error(
'Cannot publish to redis... '
'giving up',
extra={"redis_exception": str(exc)})
break

@@ -99,2 +135,3 @@

connect = False
_, error = self._get_redis_module_and_error()
while True:

@@ -108,6 +145,7 @@ try:

yield message
except RedisError:
except error as exc:
self._get_logger().error('Cannot receive from redis... '
'retrying in '
'{} secs'.format(retry_sleep))
f'{retry_sleep} secs',
extra={"redis_exception": str(exc)})
connect = True

@@ -114,0 +152,0 @@ await asyncio.sleep(retry_sleep)

@@ -376,11 +376,11 @@ import asyncio

@eio.on('connect')
def on_connect(sid, environ):
async def on_connect(sid, environ):
username = authenticate_user(environ)
if not username:
return False
with eio.session(sid) as session:
async with eio.session(sid) as session:
session['username'] = username
@eio.on('message')
def on_message(sid, msg):
async def on_message(sid, msg):
async with eio.session(sid) as session:

@@ -387,0 +387,0 @@ print('received message from ', session['username'])

@@ -108,3 +108,3 @@ import asyncio

"""
return self.client.transport if self.client else ''
return self.client.transport() if self.client else ''

@@ -167,2 +167,4 @@ async def emit(self, event, data=None):

timeout=timeout)
except TimeoutError:
raise
except SocketIOError:

@@ -169,0 +171,0 @@ pass

@@ -96,2 +96,3 @@ import itertools

self.namespaces = {} #: set of connected namespaces.
self.failed_namespaces = []
self.handlers = {}

@@ -98,0 +99,0 @@ self.namespace_handlers = {}

@@ -140,2 +140,3 @@ import random

self.namespaces = {}
self.failed_namespaces = []
if self._connect_event is None:

@@ -165,3 +166,4 @@ self._connect_event = self.eio.create_event()

self._connect_event.clear()
if set(self.namespaces) == set(self.connection_namespaces):
if len(self.namespaces) + len(self.failed_namespaces) == \
len(self.connection_namespaces):
break

@@ -171,3 +173,4 @@ if set(self.namespaces) != set(self.connection_namespaces):

raise exceptions.ConnectionError(
'One or more namespaces failed to connect')
'One or more namespaces failed to connect: '
', '.join(self.failed_namespaces))

@@ -390,3 +393,3 @@ self.connected = True

self.connected = False
self.eio.disconnect(abort=True)
self.eio.disconnect()

@@ -432,2 +435,3 @@ def _handle_event(self, namespace, id, data):

self._trigger_event('connect_error', namespace, *data)
self.failed_namespaces.append(namespace)
self._connect_event.set()

@@ -447,3 +451,3 @@ if namespace in self.namespaces:

return handler(*args)
except TypeError:
except TypeError: # pragma: no cover
# the legacy disconnect event does not take a reason argument

@@ -450,0 +454,0 @@ if event == 'disconnect':

import logging
import pickle

@@ -9,2 +8,3 @@ try:

from engineio import json
from .pubsub_manager import PubSubManager

@@ -57,3 +57,3 @@

def _publish(self, data):
self.producer.send(self.channel, value=pickle.dumps(data))
self.producer.send(self.channel, value=json.dumps(data))
self.producer.flush()

@@ -67,2 +67,2 @@

if message.topic == self.channel:
yield pickle.loads(message.value)
yield message.value

@@ -1,2 +0,1 @@

import pickle
import time

@@ -10,2 +9,3 @@ import uuid

from engineio import json
from .pubsub_manager import PubSubManager

@@ -106,3 +106,3 @@

self.publisher_connection)
producer_publish(pickle.dumps(data))
producer_publish(json.dumps(data))
break

@@ -109,0 +109,0 @@ except (OSError, kombu.exceptions.KombuError):

@@ -5,3 +5,2 @@ from functools import partial

from engineio import json
import pickle

@@ -200,12 +199,6 @@ from .manager import Manager

else:
if isinstance(message, bytes): # pragma: no cover
try:
data = pickle.loads(message)
except:
pass
if data is None:
try:
data = json.loads(message)
except:
pass
try:
data = json.loads(message)
except:
pass
if data and 'method' in data:

@@ -212,0 +205,0 @@ self._get_logger().debug('pubsub message: {}'.format(

import logging
import pickle
import time
from urllib.parse import urlparse
try:
try: # pragma: no cover
import redis
from redis.exceptions import RedisError
except ImportError:
redis = None
RedisError = None
try: # pragma: no cover
import valkey
from valkey.exceptions import ValkeyError
except ImportError:
valkey = None
ValkeyError = None
from engineio import json
from .pubsub_manager import PubSubManager

@@ -21,3 +30,3 @@

parsed_url = urlparse(url)
if parsed_url.scheme != 'redis+sentinel':
if parsed_url.scheme not in {'redis+sentinel', 'valkey+sentinel'}:
raise ValueError('Invalid Redis Sentinel URL')

@@ -75,10 +84,11 @@ sentinels = []

write_only=False, logger=None, redis_options=None):
if redis is None:
if redis is None and valkey is None:
raise RuntimeError('Redis package is not installed '
'(Run "pip install redis" in your '
'virtualenv).')
'(Run "pip install redis" '
'or "pip install valkey" '
'in your virtualenv).')
super().__init__(channel=channel, write_only=write_only, logger=logger)
self.redis_url = url
self.redis_options = redis_options or {}
self._redis_connect()
super().__init__(channel=channel, write_only=write_only, logger=logger)

@@ -100,7 +110,24 @@ def initialize(self):

def _get_redis_module_and_error(self):
parsed_url = urlparse(self.redis_url)
schema = parsed_url.scheme.split('+', 1)[0].lower()
if schema == 'redis':
if redis is None or RedisError is None:
raise RuntimeError('Redis package is not installed '
'(Run "pip install redis" '
'in your virtualenv).')
return redis, RedisError
if schema == 'valkey':
if valkey is None or ValkeyError is None:
raise RuntimeError('Valkey package is not installed '
'(Run "pip install valkey" '
'in your virtualenv).')
return valkey, ValkeyError
error_msg = f'Unsupported Redis URL schema: {schema}'
raise ValueError(error_msg)
def _redis_connect(self):
if not self.redis_url.startswith('redis+sentinel://'):
self.redis = redis.Redis.from_url(self.redis_url,
**self.redis_options)
else:
module, _ = self._get_redis_module_and_error()
parsed_url = urlparse(self.redis_url)
if parsed_url.scheme in {"redis+sentinel", "valkey+sentinel"}:
sentinels, service_name, connection_kwargs = \

@@ -110,4 +137,7 @@ parse_redis_sentinel_url(self.redis_url)

kwargs.update(connection_kwargs)
sentinel = redis.sentinel.Sentinel(sentinels, **kwargs)
sentinel = module.sentinel.Sentinel(sentinels, **kwargs)
self.redis = sentinel.master_for(service_name or self.channel)
else:
self.redis = module.Redis.from_url(self.redis_url,
**self.redis_options)
self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)

@@ -117,2 +147,3 @@

retry = True
_, error = self._get_redis_module_and_error()
while True:

@@ -122,9 +153,15 @@ try:

self._redis_connect()
return self.redis.publish(self.channel, pickle.dumps(data))
except redis.exceptions.RedisError:
return self.redis.publish(self.channel, json.dumps(data))
except error as exc:
if retry:
logger.error('Cannot publish to redis... retrying')
logger.error(
'Cannot publish to redis... retrying',
extra={"redis_exception": str(exc)}
)
retry = False
else:
logger.error('Cannot publish to redis... giving up')
logger.error(
'Cannot publish to redis... giving up',
extra={"redis_exception": str(exc)}
)
break

@@ -135,2 +172,3 @@

connect = False
_, error = self._get_redis_module_and_error()
while True:

@@ -143,5 +181,6 @@ try:

yield from self.pubsub.listen()
except redis.exceptions.RedisError:
except error as exc:
logger.error('Cannot receive from redis... '
'retrying in {} secs'.format(retry_sleep))
f'retrying in {retry_sleep} secs',
extra={"redis_exception": str(exc)})
connect = True

@@ -148,0 +187,0 @@ time.sleep(retry_sleep)

@@ -106,3 +106,3 @@ from threading import Event

"""
return self.client.transport if self.client else ''
return self.client.transport() if self.client else ''

@@ -159,2 +159,4 @@ def emit(self, event, data=None):

timeout=timeout)
except TimeoutError:
raise
except SocketIOError:

@@ -161,0 +163,0 @@ pass

@@ -1,4 +0,4 @@

import pickle
import re
from engineio import json
from .pubsub_manager import PubSubManager

@@ -60,2 +60,3 @@

super().__init__(channel=channel, write_only=write_only, logger=logger)
url = url.replace('zmq+', '')

@@ -76,6 +77,5 @@ (sink_url, sub_port) = url.split('+')

self.channel = channel
super().__init__(channel=channel, write_only=write_only, logger=logger)
def _publish(self, data):
pickled_data = pickle.dumps(
packed_data = json.dumps(
{

@@ -86,4 +86,4 @@ 'type': 'message',

}
)
return self.sink.send(pickled_data)
).encode()
return self.sink.send(packed_data)

@@ -100,3 +100,3 @@ def zmq_listen(self):

try:
message = pickle.loads(message)
message = json.loads(message)
except Exception:

@@ -103,0 +103,0 @@ pass

@@ -110,5 +110,5 @@ from functools import wraps

def test_admin_connect_with_no_auth(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin')
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',

@@ -119,6 +119,6 @@ auth={'foo': 'bar'})

def test_admin_connect_with_dict_auth(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):

@@ -128,3 +128,3 @@ admin_client.connect(

auth={'foo': 'baz'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):

@@ -137,13 +137,13 @@ admin_client.connect(

def test_admin_connect_with_list_auth(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',
auth={'u': 'admin', 'p': 'secret'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):
admin_client.connect('http://localhost:8900',
namespace='/admin', auth={'foo': 'baz'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):

@@ -155,10 +155,10 @@ admin_client.connect('http://localhost:8900',

def test_admin_connect_with_function_auth(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):
admin_client.connect('http://localhost:8900',
namespace='/admin', auth={'foo': 'baz'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):

@@ -170,10 +170,10 @@ admin_client.connect('http://localhost:8900',

def test_admin_connect_with_async_function_auth(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):
admin_client.connect('http://localhost:8900',
namespace='/admin', auth={'foo': 'baz'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):

@@ -185,3 +185,3 @@ admin_client.connect('http://localhost:8900',

def test_admin_connect_only_admin(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin')

@@ -211,6 +211,6 @@ sid = admin_client.sid

def test_admin_connect_with_others(self):
with socketio.SimpleClient() as client1, \
socketio.SimpleClient() as client2, \
socketio.SimpleClient() as client3, \
socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as client1, \
socketio.SimpleClient(reconnection=False) as client2, \
socketio.SimpleClient(reconnection=False) as client3, \
socketio.SimpleClient(reconnection=False) as admin_client:
client1.connect('http://localhost:8900')

@@ -262,3 +262,3 @@ client1.emit('enter_room', 'room')

def test_admin_connect_production(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin')

@@ -284,5 +284,5 @@ events = self._expect({'config': 1, 'server_stats': 2},

def test_admin_features(self):
with socketio.SimpleClient() as client1, \
socketio.SimpleClient() as client2, \
socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as client1, \
socketio.SimpleClient(reconnection=False) as client2, \
socketio.SimpleClient(reconnection=False) as admin_client:
client1.connect('http://localhost:8900')

@@ -289,0 +289,0 @@ client2.connect('http://localhost:8900')

import asyncio
import functools
import json
from unittest import mock

@@ -485,4 +486,2 @@

async def messages():
import pickle
yield {'method': 'emit', 'value': 'foo', 'host_id': 'x'}

@@ -494,4 +493,4 @@ yield {'missing': 'method', 'host_id': 'x'}

yield {'method': 'bogus', 'host_id': 'x'}
yield pickle.dumps({'method': 'close_room', 'value': 'baz',
'host_id': 'x'})
yield json.dumps({'method': 'close_room', 'value': 'baz',
'host_id': 'x'})
yield {'method': 'enter_room', 'sid': '123', 'namespace': '/foo',

@@ -502,3 +501,3 @@ 'room': 'room', 'host_id': 'x'}

yield 'bad json'
yield b'bad pickled'
yield b'bad data'

@@ -511,4 +510,4 @@ # these should not publish anything on the queue, as they come from

'host_id': host_id}
yield pickle.dumps({'method': 'close_room', 'value': 'baz',
'host_id': host_id})
yield json.dumps({'method': 'close_room', 'value': 'baz',
'host_id': host_id})

@@ -515,0 +514,0 @@ self.pm._listen = messages

@@ -75,3 +75,3 @@ import asyncio

client = AsyncSimpleClient()
client.client = mock.MagicMock(transport='websocket')
client.client = mock.MagicMock(transport=lambda: 'websocket')
client.client.get_sid.return_value = 'sid'

@@ -146,2 +146,13 @@ client.connected_event.set()

async def test_call_timeout(self):
client = AsyncSimpleClient()
client.connected_event.set()
client.connected = True
client.client = mock.MagicMock()
client.client.call = mock.AsyncMock()
client.client.call.side_effect = TimeoutError()
with pytest.raises(TimeoutError):
await client.call('foo', 'bar')
async def test_receive_with_input_buffer(self):

@@ -148,0 +159,0 @@ client = AsyncSimpleClient()

@@ -100,5 +100,5 @@ from functools import wraps

def test_admin_connect_with_no_auth(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin')
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',

@@ -109,6 +109,6 @@ auth={'foo': 'bar'})

def test_admin_connect_with_dict_auth(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):

@@ -118,3 +118,3 @@ admin_client.connect(

auth={'foo': 'baz'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):

@@ -127,13 +127,13 @@ admin_client.connect(

def test_admin_connect_with_list_auth(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',
auth={'u': 'admin', 'p': 'secret'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):
admin_client.connect('http://localhost:8900',
namespace='/admin', auth={'foo': 'baz'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):

@@ -145,10 +145,10 @@ admin_client.connect('http://localhost:8900',

def test_admin_connect_with_function_auth(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin',
auth={'foo': 'bar'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):
admin_client.connect('http://localhost:8900',
namespace='/admin', auth={'foo': 'baz'})
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
with pytest.raises(ConnectionError):

@@ -160,3 +160,3 @@ admin_client.connect('http://localhost:8900',

def test_admin_connect_only_admin(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin')

@@ -186,6 +186,6 @@ sid = admin_client.sid

def test_admin_connect_with_others(self):
with socketio.SimpleClient() as client1, \
socketio.SimpleClient() as client2, \
socketio.SimpleClient() as client3, \
socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as client1, \
socketio.SimpleClient(reconnection=False) as client2, \
socketio.SimpleClient(reconnection=False) as client3, \
socketio.SimpleClient(reconnection=False) as admin_client:
client1.connect('http://localhost:8900')

@@ -237,3 +237,3 @@ client1.emit('enter_room', 'room')

def test_admin_connect_production(self):
with socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as admin_client:
admin_client.connect('http://localhost:8900', namespace='/admin')

@@ -259,5 +259,5 @@ events = self._expect({'config': 1, 'server_stats': 2},

def test_admin_features(self):
with socketio.SimpleClient() as client1, \
socketio.SimpleClient() as client2, \
socketio.SimpleClient() as admin_client:
with socketio.SimpleClient(reconnection=False) as client1, \
socketio.SimpleClient(reconnection=False) as client2, \
socketio.SimpleClient(reconnection=False) as admin_client:
client1.connect('http://localhost:8900')

@@ -264,0 +264,0 @@ client2.connect('http://localhost:8900')

import functools
import json
import logging

@@ -468,4 +469,2 @@ from unittest import mock

def messages():
import pickle
yield {'method': 'emit', 'value': 'foo', 'host_id': 'x'}

@@ -477,4 +476,4 @@ yield {'missing': 'method', 'host_id': 'x'}

yield {'method': 'bogus', 'host_id': 'x'}
yield pickle.dumps({'method': 'close_room', 'value': 'baz',
'host_id': 'x'})
yield json.dumps({'method': 'close_room', 'value': 'baz',
'host_id': 'x'})
yield {'method': 'enter_room', 'sid': '123', 'namespace': '/foo',

@@ -485,3 +484,3 @@ 'room': 'room', 'host_id': 'x'}

yield 'bad json'
yield b'bad pickled'
yield b'bad data'

@@ -494,4 +493,4 @@ # these should not publish anything on the queue, as they come from

'host_id': host_id}
yield pickle.dumps({'method': 'close_room', 'value': 'baz',
'host_id': host_id})
yield json.dumps({'method': 'close_room', 'value': 'baz',
'host_id': host_id})

@@ -498,0 +497,0 @@ self.pm._listen = mock.MagicMock(side_effect=messages)

@@ -7,8 +7,9 @@ import pytest

class TestPubSubManager:
def test_sentinel_url_parser(self):
@pytest.mark.parametrize('rtype', ['redis', 'valkey'])
def test_sentinel_url_parser(self, rtype):
with pytest.raises(ValueError):
parse_redis_sentinel_url('redis://localhost:6379/0')
parse_redis_sentinel_url(f'{rtype}://localhost:6379/0')
assert parse_redis_sentinel_url(
'redis+sentinel://localhost:6379'
f'{rtype}+sentinel://localhost:6379'
) == (

@@ -20,3 +21,3 @@ [('localhost', 6379)],

assert parse_redis_sentinel_url(
'redis+sentinel://192.168.0.1:6379,192.168.0.2:6379/'
f'{rtype}+sentinel://192.168.0.1:6379,192.168.0.2:6379/'
) == (

@@ -28,3 +29,3 @@ [('192.168.0.1', 6379), ('192.168.0.2', 6379)],

assert parse_redis_sentinel_url(
'redis+sentinel://h1:6379,h2:6379/0'
f'{rtype}+sentinel://h1:6379,h2:6379/0'
) == (

@@ -36,3 +37,4 @@ [('h1', 6379), ('h2', 6379)],

assert parse_redis_sentinel_url(
'redis+sentinel://user:password@h1:6379,h2:6379,h1:6380/0/myredis'
f'{rtype}+sentinel://'
'user:password@h1:6379,h2:6379,h1:6380/0/myredis'
) == (

@@ -39,0 +41,0 @@ [('h1', 6379), ('h2', 6379), ('h1', 6380)],

@@ -67,3 +67,3 @@ from unittest import mock

client = SimpleClient()
client.client = mock.MagicMock(transport='websocket')
client.client = mock.MagicMock(transport=lambda: 'websocket')
client.client.get_sid.return_value = 'sid'

@@ -134,2 +134,12 @@ client.connected_event.set()

def test_call_timeout(self):
client = SimpleClient()
client.connected_event.set()
client.connected = True
client.client = mock.MagicMock()
client.client.call.side_effect = TimeoutError()
with pytest.raises(TimeoutError):
client.call('foo', 'bar')
def test_receive_with_input_buffer(self):

@@ -136,0 +146,0 @@ client = SimpleClient()

@@ -50,3 +50,3 @@ import threading

self.httpd = make_server('', port, self._app_wrapper,
self.httpd = make_server('localhost', port, self._app_wrapper,
ThreadingWSGIServer, WebSocketRequestHandler)

@@ -53,0 +53,0 @@ self.thread = threading.Thread(target=self.httpd.serve_forever)