🚨 Active Supply Chain Attack:node-ipc Package Compromised.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.14.3
to
5.15.0
+8
-3
docs/client.rst

@@ -201,6 +201,11 @@ The Socket.IO Clients

while ``engineio_logger`` controls logs that originate in the low-level
Engine.IO transport. These arguments can be set to ``True`` to output logs to
``stderr``, or to an object compatible with Python's ``logging`` package
where the logs should be emitted to. A value of ``False`` disables logging.
Engine.IO transport. The value given to these arguments controls logging
behavior:
* ``True``: Enables log output to ``stderr`` at the ``INFO`` level.
* ``False``: Enables log output to ``stderr`` at the ``ERROR`` level. This is
the default.
* A ``logging.Logger`` instance: Uses the provided logger without additional
configuration.
Logging can help identify the cause of connection problems, unexpected

@@ -207,0 +212,0 @@ disconnections and other issues.

@@ -660,6 +660,11 @@ The Socket.IO Server

while ``engineio_logger`` controls logs that originate in the low-level
Engine.IO transport. These arguments can be set to ``True`` to output logs to
``stderr``, or to an object compatible with Python's ``logging`` package
where the logs should be emitted to. A value of ``False`` disables logging.
Engine.IO transport. The value given to these arguments controls logging
behavior:
* ``True``: Enables log output to ``stderr`` at the ``INFO`` level.
* ``False``: Enables log output to ``stderr`` at the ``ERROR`` level. This is
the default.
* A ``logging.Logger`` instance: Uses the provided logger without additional
configuration.
Logging can help identify the cause of connection problems, 400 responses,

@@ -666,0 +671,0 @@ bad performance and other issues.

Metadata-Version: 2.4
Name: python-socketio
Version: 5.14.3
Version: 5.15.0
Summary: Socket.IO server and client for Python

@@ -5,0 +5,0 @@ Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>

[project]
name = "python-socketio"
version = "5.14.3"
version = "5.15.0"
license = {text = "MIT"}

@@ -5,0 +5,0 @@ authors = [

Metadata-Version: 2.4
Name: python-socketio
Version: 5.14.3
Version: 5.15.0
Summary: Socket.IO server and client for Python

@@ -5,0 +5,0 @@ Author-email: Miguel Grinberg <miguel.grinberg@gmail.com>

@@ -104,11 +104,11 @@ import asyncio

async def _listen(self):
async with (await self._connection()) as connection:
channel = await self._channel(connection)
await channel.set_qos(prefetch_count=1)
exchange = await self._exchange(channel)
queue = await self._queue(channel, exchange)
retry_sleep = 1
while True:
try:
async with (await self._connection()) as connection:
channel = await self._channel(connection)
await channel.set_qos(prefetch_count=1)
exchange = await self._exchange(channel)
queue = await self._queue(channel, exchange)
retry_sleep = 1
while True:
try:
async with queue.iterator() as queue_iter:

@@ -119,10 +119,10 @@ async for message in queue_iter:

retry_sleep = 1
except aio_pika.AMQPException:
self._get_logger().error(
'Cannot receive from rabbitmq... '
'retrying in {} secs'.format(retry_sleep))
await asyncio.sleep(retry_sleep)
retry_sleep = min(retry_sleep * 2, 60)
except aio_pika.exceptions.ChannelInvalidStateError:
# aio_pika raises this exception when the task is cancelled
raise asyncio.CancelledError()
except aio_pika.AMQPException:
self._get_logger().error(
'Cannot receive from rabbitmq... '
'retrying in {} secs'.format(retry_sleep))
await asyncio.sleep(retry_sleep)
retry_sleep = min(retry_sleep * 2, 60)
except aio_pika.exceptions.ChannelInvalidStateError:
# aio_pika raises this exception when the task is cancelled
raise asyncio.CancelledError()

@@ -492,3 +492,3 @@ import asyncio

# or else, forward the event to a namepsace handler if one exists
# or else, forward the event to a namespace handler if one exists
handler, args = self._get_namespace_handler(namespace, args)

@@ -495,0 +495,0 @@ if handler:

@@ -64,3 +64,5 @@ import asyncio

self.redis_options = redis_options or {}
self._redis_connect()
self.connected = False
self.redis = None
self.pubsub = None

@@ -110,9 +112,9 @@ def _get_redis_module_and_error(self):

self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
self.connected = True
async def _publish(self, data): # pragma: no cover
retry = True
_, error = self._get_redis_module_and_error()
while True:
for retries_left in range(1, -1, -1): # 2 attempts
try:
if not retry:
if not self.connected:
self._redis_connect()

@@ -122,3 +124,3 @@ return await self.redis.publish(

except error as exc:
if retry:
if retries_left > 0:
self._get_logger().error(

@@ -128,3 +130,3 @@ 'Cannot publish to redis... '

extra={"redis_exception": str(exc)})
retry = False
self.connected = False
else:

@@ -139,8 +141,8 @@ self._get_logger().error(

async def _redis_listen_with_retries(self): # pragma: no cover
_, error = self._get_redis_module_and_error()
retry_sleep = 1
connect = False
_, error = self._get_redis_module_and_error()
subscribed = False
while True:
try:
if connect:
if not subscribed:
self._redis_connect()

@@ -156,3 +158,3 @@ await self.pubsub.subscribe(self.channel)

extra={"redis_exception": str(exc)})
connect = True
subscribed = False
await asyncio.sleep(retry_sleep)

@@ -165,3 +167,2 @@ retry_sleep *= 2

channel = self.channel.encode('utf-8')
await self.pubsub.subscribe(self.channel)
async for message in self._redis_listen_with_retries():

@@ -168,0 +169,0 @@ if message['channel'] == channel and \

@@ -118,6 +118,6 @@ import time

def _listen(self):
reader_queue = self._queue()
retry_sleep = 1
while True:
try:
reader_queue = self._queue()
with self._connection() as connection:

@@ -124,0 +124,0 @@ with connection.SimpleQueue(reader_queue) as queue:

@@ -7,10 +7,36 @@ import msgpack

uses_binary_events = False
dumps_default = None
ext_hook = msgpack.ExtType
@classmethod
def configure(cls, dumps_default=None, ext_hook=msgpack.ExtType):
"""Change the default options for msgpack encoding and decoding.
:param dumps_default: a function called for objects that cannot be
serialized by default msgpack. The function
receives one argument, the object to serialize.
It should return a serializable object or a
``msgpack.ExtType`` instance.
:param ext_hook: a function called when a ``msgpack.ExtType`` object is
seen during decoding. The function receives two
arguments, the code and the data. It should return the
decoded object.
"""
class CustomMsgPackPacket(MsgPackPacket):
dumps_default = None
ext_hook = None
CustomMsgPackPacket.dumps_default = dumps_default
CustomMsgPackPacket.ext_hook = ext_hook
return CustomMsgPackPacket
def encode(self):
"""Encode the packet for transmission."""
return msgpack.dumps(self._to_dict())
return msgpack.dumps(self._to_dict(),
default=self.__class__.dumps_default)
def decode(self, encoded_packet):
"""Decode a transmitted package."""
decoded = msgpack.loads(encoded_packet)
decoded = msgpack.loads(encoded_packet,
ext_hook=self.__class__.ext_hook)
self.packet_type = decoded['type']

@@ -17,0 +43,0 @@ self.data = decoded.get('data')

@@ -157,3 +157,3 @@ import functools

def _deconstruct_binary_internal(cls, data, attachments):
if isinstance(data, bytes):
if isinstance(data, (bytes, bytearray)):
attachments.append(data)

@@ -173,3 +173,3 @@ return {'_placeholder': True, 'num': len(attachments) - 1}

"""Check if the data contains binary components."""
if isinstance(data, bytes):
if isinstance(data, (bytes, bytearray)):
return True

@@ -176,0 +176,0 @@ elif isinstance(data, list):

@@ -86,3 +86,5 @@ import logging

self.redis_options = redis_options or {}
self._redis_connect()
self.connected = False
self.redis = None
self.pubsub = None

@@ -147,13 +149,13 @@ def initialize(self): # pragma: no cover

self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True)
self.connected = True
def _publish(self, data): # pragma: no cover
retry = True
_, error = self._get_redis_module_and_error()
while True:
for retries_left in range(1, -1, -1): # 2 attempts
try:
if not retry:
if not self.connected:
self._redis_connect()
return self.redis.publish(self.channel, json.dumps(data))
except error as exc:
if retry:
if retries_left > 0:
logger.error(

@@ -163,3 +165,3 @@ 'Cannot publish to redis... retrying',

)
retry = False
self.connected = False
else:

@@ -173,8 +175,8 @@ logger.error(

def _redis_listen_with_retries(self): # pragma: no cover
_, error = self._get_redis_module_and_error()
retry_sleep = 1
connect = False
_, error = self._get_redis_module_and_error()
subscribed = False
while True:
try:
if connect:
if not subscribed:
self._redis_connect()

@@ -188,3 +190,3 @@ self.pubsub.subscribe(self.channel)

extra={"redis_exception": str(exc)})
connect = True
subscribed = False
time.sleep(retry_sleep)

@@ -197,3 +199,2 @@ retry_sleep *= 2

channel = self.channel.encode('utf-8')
self.pubsub.subscribe(self.channel)
for message in self._redis_listen_with_retries():

@@ -200,0 +201,0 @@ if message['channel'] == channel and \

import asyncio
from unittest import mock
from datetime import datetime, timezone, timedelta

@@ -11,2 +12,3 @@ import pytest

from socketio import packet
from socketio.msgpack_packet import MsgPackPacket

@@ -1246,1 +1248,19 @@

c.start_background_task.assert_not_called()
def test_serializer_args_with_msgpack(self):
def default(o):
if isinstance(o, datetime):
return o.isoformat()
raise TypeError("Unknown type")
data = {"current": datetime.now(timezone(timedelta(0)))}
c = async_client.AsyncClient(
serializer=MsgPackPacket.configure(dumps_default=default))
p = c.packet_class(data=data)
p2 = c.packet_class(encoded_packet=p.encode())
assert p.data != p2.data
assert isinstance(p2.data, dict)
assert "current" in p2.data
assert isinstance(p2.data["current"], str)
assert default(data["current"]) == p2.data["current"]

@@ -100,2 +100,32 @@ import asyncio

async def test_emit_bytearray(self):
await self.pm.emit('foo', bytearray(b'bar'))
self.pm._publish.assert_awaited_once_with(
{
'method': 'emit',
'event': 'foo',
'binary': True,
'data': [{'_placeholder': True, 'num': 0}, 'YmFy'],
'namespace': '/',
'room': None,
'skip_sid': None,
'callback': None,
'host_id': '123456',
}
)
await self.pm.emit('foo', {'foo': bytearray(b'bar')})
self.pm._publish.assert_awaited_with(
{
'method': 'emit',
'event': 'foo',
'binary': True,
'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'],
'namespace': '/',
'room': None,
'skip_sid': None,
'callback': None,
'host_id': '123456',
}
)
async def test_emit_with_to(self):

@@ -102,0 +132,0 @@ sid = 'room-mate'

@@ -15,3 +15,3 @@ import pytest

with pytest.raises(RuntimeError):
AsyncRedisManager('redis://')
AsyncRedisManager('redis://')._redis_connect()
assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None

@@ -26,3 +26,3 @@

with pytest.raises(RuntimeError):
AsyncRedisManager('valkey://')
AsyncRedisManager('valkey://')._redis_connect()
assert AsyncRedisManager('unix:///var/sock/redis.sock') is not None

@@ -39,7 +39,7 @@

with pytest.raises(RuntimeError):
AsyncRedisManager('redis://')
AsyncRedisManager('redis://')._redis_connect()
with pytest.raises(RuntimeError):
AsyncRedisManager('valkey://')
AsyncRedisManager('valkey://')._redis_connect()
with pytest.raises(RuntimeError):
AsyncRedisManager('unix:///var/sock/redis.sock')
AsyncRedisManager('unix:///var/sock/redis.sock')._redis_connect()

@@ -51,3 +51,3 @@ async_redis_manager.aioredis = saved_redis

with pytest.raises(ValueError):
AsyncRedisManager('http://localhost:6379')
AsyncRedisManager('http://localhost:6379')._redis_connect()

@@ -79,2 +79,4 @@ def test_redis_connect(self):

c = AsyncRedisManager(url)
assert c.redis is None
c._redis_connect()
assert isinstance(c.redis, redis.asyncio.Redis)

@@ -110,4 +112,6 @@

c = AsyncRedisManager(url)
assert c.redis is None
c._redis_connect()
assert isinstance(c.redis, valkey.asyncio.Valkey)
async_redis_manager.aioredis = saved_redis
import asyncio
import logging
from unittest import mock
from datetime import datetime, timezone, timedelta

@@ -14,2 +15,3 @@ from engineio import json

from socketio import packet
from socketio.msgpack_packet import MsgPackPacket

@@ -1093,1 +1095,19 @@

s.eio.sleep.assert_awaited_once_with(1.23)
def test_serializer_args_with_msgpack(self, eio):
def default(o):
if isinstance(o, datetime):
return o.isoformat()
raise TypeError("Unknown type")
data = {"current": datetime.now(timezone(timedelta(0)))}
s = async_server.AsyncServer(
serializer=MsgPackPacket.configure(dumps_default=default))
p = s.packet_class(data=data)
p2 = s.packet_class(encoded_packet=p.encode())
assert p.data != p2.data
assert isinstance(p2.data, dict)
assert "current" in p2.data
assert isinstance(p2.data["current"], str)
assert default(data["current"]) == p2.data["current"]
import logging
import time
from unittest import mock
from datetime import datetime, timezone, timedelta

@@ -16,2 +17,3 @@ from engineio import exceptions as engineio_exceptions

from socketio import packet
from socketio.msgpack_packet import MsgPackPacket

@@ -1390,1 +1392,19 @@

c.start_background_task.assert_not_called()
def test_serializer_args_with_msgpack(self):
def default(o):
if isinstance(o, datetime):
return o.isoformat()
raise TypeError("Unknown type")
data = {"current": datetime.now(timezone(timedelta(0)))}
c = client.Client(
serializer=MsgPackPacket.configure(dumps_default=default))
p = c.packet_class(data=data)
p2 = c.packet_class(encoded_packet=p.encode())
assert p.data != p2.data
assert isinstance(p2.data, dict)
assert "current" in p2.data
assert isinstance(p2.data["current"], str)
assert default(data["current"]) == p2.data["current"]

@@ -0,1 +1,6 @@

from datetime import datetime, timedelta, timezone
import pytest
import msgpack
from socketio import msgpack_packet

@@ -35,1 +40,100 @@ from socketio import packet

assert p2.data == {'foo': b'bar'}
def test_encode_with_dumps_default(self):
def default(obj):
if isinstance(obj, datetime):
return obj.isoformat()
raise TypeError('Unknown type')
data = {
'current': datetime.now(tz=timezone(timedelta(0))),
'key': 'value',
}
p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)(
data=data)
p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
assert p.packet_type == p2.packet_type
assert p.id == p2.id
assert p.namespace == p2.namespace
assert p.data != p2.data
assert isinstance(p2.data, dict)
assert 'current' in p2.data
assert isinstance(p2.data['current'], str)
assert default(data['current']) == p2.data['current']
data.pop('current')
p2_data_without_current = p2.data.copy()
p2_data_without_current.pop('current')
assert data == p2_data_without_current
def test_encode_without_dumps_default(self):
data = {
'current': datetime.now(tz=timezone(timedelta(0))),
'key': 'value',
}
p_without_default = msgpack_packet.MsgPackPacket(data=data)
with pytest.raises(TypeError):
p_without_default.encode()
def test_encode_decode_with_ext_hook(self):
class Custom:
def __init__(self, value):
self.value = value
def __eq__(self, value: object) -> bool:
return isinstance(value, Custom) and self.value == value.value
def default(obj):
if isinstance(obj, Custom):
return msgpack.ExtType(1, obj.value)
raise TypeError('Unknown type')
def ext_hook(code, data):
if code == 1:
return Custom(data)
raise TypeError('Unknown ext type')
data = {'custom': Custom(b'custom_data'), 'key': 'value'}
p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)(
data=data)
p2 = msgpack_packet.MsgPackPacket.configure(ext_hook=ext_hook)(
encoded_packet=p.encode()
)
assert p.packet_type == p2.packet_type
assert p.id == p2.id
assert p.data == p2.data
assert p.namespace == p2.namespace
def test_encode_decode_without_ext_hook(self):
class Custom:
def __init__(self, value):
self.value = value
def __eq__(self, value: object) -> bool:
return isinstance(value, Custom) and self.value == value.value
def default(obj):
if isinstance(obj, Custom):
return msgpack.ExtType(1, obj.value)
raise TypeError('Unknown type')
data = {'custom': Custom(b'custom_data'), 'key': 'value'}
p = msgpack_packet.MsgPackPacket.configure(dumps_default=default)(
data=data)
p2 = msgpack_packet.MsgPackPacket(encoded_packet=p.encode())
assert p.packet_type == p2.packet_type
assert p.id == p2.id
assert p.namespace == p2.namespace
assert p.data != p2.data
assert isinstance(p2.data, dict)
assert 'custom' in p2.data
assert isinstance(p2.data['custom'], msgpack.ExtType)
assert p2.data['custom'].code == 1
assert p2.data['custom'].data == b'custom_data'
data.pop('custom')
p2_data_without_custom = p2.data.copy()
p2_data_without_custom.pop('custom')
assert data == p2_data_without_custom

@@ -282,3 +282,5 @@ import pytest

assert pkt.data_is_binary([b'foo'])
assert pkt.data_is_binary([bytearray(b'foo')])
assert pkt.data_is_binary(['foo', b'bar'])
assert pkt.data_is_binary(['foo', bytearray(b'bar')])

@@ -290,2 +292,4 @@ def test_data_is_binary_dict(self):

assert pkt.data_is_binary({'a': b'foo'})
assert pkt.data_is_binary({'a': bytearray(b'foo')})
assert pkt.data_is_binary({'a': 'foo', 'b': b'bar'})
assert pkt.data_is_binary({'a': 'foo', 'b': bytearray(b'bar')})

@@ -112,2 +112,32 @@ import functools

def test_emit_bytearray(self):
self.pm.emit('foo', bytearray(b'bar'))
self.pm._publish.assert_called_once_with(
{
'method': 'emit',
'event': 'foo',
'binary': True,
'data': [{'_placeholder': True, 'num': 0}, 'YmFy'],
'namespace': '/',
'room': None,
'skip_sid': None,
'callback': None,
'host_id': '123456',
}
)
self.pm.emit('foo', {'foo': bytearray(b'bar')})
self.pm._publish.assert_called_with(
{
'method': 'emit',
'event': 'foo',
'binary': True,
'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'],
'namespace': '/',
'room': None,
'skip_sid': None,
'callback': None,
'host_id': '123456',
}
)
def test_emit_with_to(self):

@@ -114,0 +144,0 @@ sid = "ferris"

@@ -15,3 +15,3 @@ import pytest

with pytest.raises(RuntimeError):
RedisManager('redis://')
RedisManager('redis://')._redis_connect()
assert RedisManager('unix:///var/sock/redis.sock') is not None

@@ -26,3 +26,3 @@

with pytest.raises(RuntimeError):
RedisManager('valkey://')
RedisManager('valkey://')._redis_connect()
assert RedisManager('unix:///var/sock/redis.sock') is not None

@@ -39,7 +39,7 @@

with pytest.raises(RuntimeError):
RedisManager('redis://')
RedisManager('redis://')._redis_connect()
with pytest.raises(RuntimeError):
RedisManager('valkey://')
RedisManager('valkey://')._redis_connect()
with pytest.raises(RuntimeError):
RedisManager('unix:///var/sock/redis.sock')
RedisManager('unix:///var/sock/redis.sock')._redis_connect()

@@ -51,3 +51,3 @@ redis_manager.redis = saved_redis

with pytest.raises(ValueError):
RedisManager('http://localhost:6379')
RedisManager('http://localhost:6379')._redis_connect()

@@ -79,2 +79,4 @@ def test_redis_connect(self):

c = RedisManager(url)
assert c.redis is None
c._redis_connect()
assert isinstance(c.redis, redis.Redis)

@@ -110,2 +112,4 @@

c = RedisManager(url)
assert c.redis is None
c._redis_connect()
assert isinstance(c.redis, valkey.Valkey)

@@ -112,0 +116,0 @@

import logging
from unittest import mock
from datetime import datetime, timezone, timedelta

@@ -13,2 +14,3 @@ from engineio import json

from socketio import server
from socketio.msgpack_packet import MsgPackPacket

@@ -1036,1 +1038,19 @@

s.eio.sleep.assert_called_once_with(1.23)
def test_serializer_args_with_msgpack(self, eio):
def default(o):
if isinstance(o, datetime):
return o.isoformat()
raise TypeError("Unknown type")
data = {"current": datetime.now(timezone(timedelta(0)))}
s = server.Server(
serializer=MsgPackPacket.configure(dumps_default=default))
p = s.packet_class(data=data)
p2 = s.packet_class(encoded_packet=p.encode())
assert p.data != p2.data
assert isinstance(p2.data, dict)
assert "current" in p2.data
assert isinstance(p2.data["current"], str)
assert default(data["current"]) == p2.data["current"]