python-socketio
Advanced tools
+3
-1
| Metadata-Version: 2.4 | ||
| Name: python-socketio | ||
| Version: 5.14.1 | ||
| Version: 5.14.2 | ||
| Summary: Socket.IO server and client for Python | ||
@@ -23,2 +23,4 @@ Author-email: Miguel Grinberg <miguel.grinberg@gmail.com> | ||
| Requires-Dist: aiohttp>=3.4; extra == "asyncio-client" | ||
| Provides-Extra: dev | ||
| Requires-Dist: tox; extra == "dev" | ||
| Provides-Extra: docs | ||
@@ -25,0 +27,0 @@ Requires-Dist: sphinx; extra == "docs" |
+4
-1
| [project] | ||
| name = "python-socketio" | ||
| version = "5.14.1" | ||
| version = "5.14.2" | ||
| license = {text = "MIT"} | ||
@@ -37,2 +37,5 @@ authors = [ | ||
| ] | ||
| dev = [ | ||
| "tox", | ||
| ] | ||
| docs = [ | ||
@@ -39,0 +42,0 @@ "sphinx", |
| Metadata-Version: 2.4 | ||
| Name: python-socketio | ||
| Version: 5.14.1 | ||
| Version: 5.14.2 | ||
| Summary: Socket.IO server and client for Python | ||
@@ -23,2 +23,4 @@ Author-email: Miguel Grinberg <miguel.grinberg@gmail.com> | ||
| Requires-Dist: aiohttp>=3.4; extra == "asyncio-client" | ||
| Provides-Extra: dev | ||
| Requires-Dist: tox; extra == "dev" | ||
| Provides-Extra: docs | ||
@@ -25,0 +27,0 @@ Requires-Dist: sphinx; extra == "docs" |
@@ -11,3 +11,6 @@ bidict>=0.21.0 | ||
| [dev] | ||
| tox | ||
| [docs] | ||
| sphinx |
@@ -178,4 +178,4 @@ import asyncio | ||
| raise exceptions.ConnectionError( | ||
| 'One or more namespaces failed to connect' | ||
| ', '.join(self.failed_namespaces)) | ||
| 'One or more namespaces failed to connect: ' | ||
| + ', '.join(self.failed_namespaces)) | ||
@@ -182,0 +182,0 @@ self.connected = True |
| import asyncio | ||
| import base64 | ||
| from functools import partial | ||
@@ -8,2 +9,3 @@ import uuid | ||
| from .async_manager import AsyncManager | ||
| from .packet import Packet | ||
@@ -68,4 +70,8 @@ | ||
| callback = None | ||
| binary = Packet.data_is_binary(data) | ||
| if binary: | ||
| data, attachments = Packet.deconstruct_binary(data) | ||
| data = [data, *[base64.b64encode(a).decode() for a in attachments]] | ||
| message = {'method': 'emit', 'event': event, 'data': data, | ||
| 'namespace': namespace, 'room': room, | ||
| 'binary': binary, 'namespace': namespace, 'room': room, | ||
| 'skip_sid': skip_sid, 'callback': callback, | ||
@@ -150,3 +156,7 @@ 'host_id': self.host_id} | ||
| callback = None | ||
| await super().emit(message['event'], message['data'], | ||
| data = message['data'] | ||
| if message.get('binary'): | ||
| attachments = [base64.b64decode(a) for a in data[1:]] | ||
| data = Packet.reconstruct_binary(data[0], attachments) | ||
| await super().emit(message['event'], data, | ||
| namespace=message.get('namespace'), | ||
@@ -153,0 +163,0 @@ room=message.get('room'), |
@@ -32,3 +32,9 @@ import itertools | ||
| def get_participants(self, namespace, room): | ||
| """Return an iterable with the active participants in a room.""" | ||
| """Return an iterable with the active participants in a room. | ||
| Note that in a multi-server scenario this method only returns the | ||
| participants connect to the server in which the method is called. There | ||
| is currently no functionality to assemble a complete list of users | ||
| across multiple servers. | ||
| """ | ||
| ns = self.rooms.get(namespace, {}) | ||
@@ -35,0 +41,0 @@ if hasattr(room, '__len__') and not isinstance(room, str): |
@@ -172,3 +172,3 @@ import random | ||
| 'One or more namespaces failed to connect: ' | ||
| ', '.join(self.failed_namespaces)) | ||
| + ', '.join(self.failed_namespaces)) | ||
@@ -175,0 +175,0 @@ self.connected = True |
+22
-18
@@ -32,3 +32,3 @@ import functools | ||
| if self.uses_binary_events and \ | ||
| (binary or (binary is None and self._data_is_binary( | ||
| (binary or (binary is None and self.data_is_binary( | ||
| self.data))): | ||
@@ -55,3 +55,3 @@ if self.packet_type == EVENT: | ||
| if self.packet_type == BINARY_EVENT or self.packet_type == BINARY_ACK: | ||
| data, attachments = self._deconstruct_binary(self.data) | ||
| data, attachments = self.deconstruct_binary(self.data) | ||
| encoded_packet += str(len(attachments)) + '-' | ||
@@ -124,16 +124,17 @@ else: | ||
| if self.attachment_count == len(self.attachments): | ||
| self.reconstruct_binary(self.attachments) | ||
| self.data = self.reconstruct_binary(self.data, self.attachments) | ||
| return True | ||
| return False | ||
| def reconstruct_binary(self, attachments): | ||
| @classmethod | ||
| def reconstruct_binary(cls, data, attachments): | ||
| """Reconstruct a decoded packet using the given list of binary | ||
| attachments. | ||
| """ | ||
| self.data = self._reconstruct_binary_internal(self.data, | ||
| self.attachments) | ||
| return cls._reconstruct_binary_internal(data, attachments) | ||
| def _reconstruct_binary_internal(self, data, attachments): | ||
| @classmethod | ||
| def _reconstruct_binary_internal(cls, data, attachments): | ||
| if isinstance(data, list): | ||
| return [self._reconstruct_binary_internal(item, attachments) | ||
| return [cls._reconstruct_binary_internal(item, attachments) | ||
| for item in data] | ||
@@ -144,4 +145,4 @@ elif isinstance(data, dict): | ||
| else: | ||
| return {key: self._reconstruct_binary_internal(value, | ||
| attachments) | ||
| return {key: cls._reconstruct_binary_internal(value, | ||
| attachments) | ||
| for key, value in data.items()} | ||
@@ -151,9 +152,11 @@ else: | ||
| def _deconstruct_binary(self, data): | ||
| @classmethod | ||
| def deconstruct_binary(cls, data): | ||
| """Extract binary components in the packet.""" | ||
| attachments = [] | ||
| data = self._deconstruct_binary_internal(data, attachments) | ||
| data = cls._deconstruct_binary_internal(data, attachments) | ||
| return data, attachments | ||
| def _deconstruct_binary_internal(self, data, attachments): | ||
| @classmethod | ||
| def _deconstruct_binary_internal(cls, data, attachments): | ||
| if isinstance(data, bytes): | ||
@@ -163,6 +166,6 @@ attachments.append(data) | ||
| elif isinstance(data, list): | ||
| return [self._deconstruct_binary_internal(item, attachments) | ||
| return [cls._deconstruct_binary_internal(item, attachments) | ||
| for item in data] | ||
| elif isinstance(data, dict): | ||
| return {key: self._deconstruct_binary_internal(value, attachments) | ||
| return {key: cls._deconstruct_binary_internal(value, attachments) | ||
| for key, value in data.items()} | ||
@@ -172,3 +175,4 @@ else: | ||
| def _data_is_binary(self, data): | ||
| @classmethod | ||
| def data_is_binary(cls, data): | ||
| """Check if the data contains binary components.""" | ||
@@ -179,7 +183,7 @@ if isinstance(data, bytes): | ||
| return functools.reduce( | ||
| lambda a, b: a or b, [self._data_is_binary(item) | ||
| lambda a, b: a or b, [cls.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) | ||
| lambda a, b: a or b, [cls.data_is_binary(item) | ||
| for item in data.values()], | ||
@@ -186,0 +190,0 @@ False) |
@@ -0,1 +1,2 @@ | ||
| import base64 | ||
| from functools import partial | ||
@@ -7,2 +8,3 @@ import uuid | ||
| from .manager import Manager | ||
| from .packet import Packet | ||
@@ -65,4 +67,8 @@ | ||
| callback = None | ||
| binary = Packet.data_is_binary(data) | ||
| if binary: | ||
| data, attachments = Packet.deconstruct_binary(data) | ||
| data = [data, *[base64.b64encode(a).decode() for a in attachments]] | ||
| message = {'method': 'emit', 'event': event, 'data': data, | ||
| 'namespace': namespace, 'room': room, | ||
| 'binary': binary, 'namespace': namespace, 'room': room, | ||
| 'skip_sid': skip_sid, 'callback': callback, | ||
@@ -146,3 +152,7 @@ 'host_id': self.host_id} | ||
| callback = None | ||
| super().emit(message['event'], message['data'], | ||
| data = message['data'] | ||
| if message.get('binary'): | ||
| attachments = [base64.b64decode(a) for a in data[1:]] | ||
| data = Packet.reconstruct_binary(data[0], attachments) | ||
| super().emit(message['event'], data, | ||
| namespace=message.get('namespace'), | ||
@@ -149,0 +159,0 @@ room=message.get('room'), |
@@ -206,2 +206,56 @@ import asyncio | ||
| async def test_connect_wait_one_namespaces_error(self): | ||
| c = async_client.AsyncClient() | ||
| c.eio.connect = mock.AsyncMock() | ||
| c._connect_event = mock.MagicMock() | ||
| async def mock_connect(): | ||
| if c.failed_namespaces == []: | ||
| c.failed_namespaces = ['/foo'] | ||
| return True | ||
| return False | ||
| c._connect_event.wait = mock_connect | ||
| with pytest.raises(exceptions.ConnectionError, | ||
| match='failed to connect: /foo'): | ||
| await c.connect( | ||
| 'url', | ||
| namespaces=['/foo'], | ||
| wait=True, | ||
| wait_timeout=0.01, | ||
| ) | ||
| assert c.connected is False | ||
| assert c.namespaces == {} | ||
| assert c.failed_namespaces == ['/foo'] | ||
| async def test_connect_wait_three_namespaces_error(self): | ||
| c = async_client.AsyncClient() | ||
| c.eio.connect = mock.AsyncMock() | ||
| c._connect_event = mock.MagicMock() | ||
| async def mock_connect(): | ||
| if c.namespaces == {}: | ||
| c.namespaces = {'/bar': '123'} | ||
| return True | ||
| elif c.namespaces == {'/bar': '123'} and c.failed_namespaces == []: | ||
| c.failed_namespaces = ['/baz'] | ||
| return True | ||
| elif c.failed_namespaces == ['/baz']: | ||
| c.failed_namespaces = ['/baz', '/foo'] | ||
| return True | ||
| return False | ||
| c._connect_event.wait = mock_connect | ||
| with pytest.raises(exceptions.ConnectionError, | ||
| match='failed to connect: /baz, /foo'): | ||
| await c.connect( | ||
| 'url', | ||
| namespaces=['/foo', '/bar', '/baz'], | ||
| wait=True, | ||
| wait_timeout=0.01, | ||
| ) | ||
| assert c.connected is False | ||
| assert c.namespaces == {'/bar': '123'} | ||
| assert c.failed_namespaces == ['/baz', '/foo'] | ||
| async def test_connect_timeout(self): | ||
@@ -208,0 +262,0 @@ c = async_client.AsyncClient() |
@@ -60,2 +60,3 @@ import asyncio | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -70,2 +71,32 @@ 'namespace': '/', | ||
| async def test_emit_binary(self): | ||
| await self.pm.emit('foo', 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': 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): | ||
@@ -78,2 +109,3 @@ sid = 'room-mate' | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -94,2 +126,3 @@ 'namespace': '/', | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -110,2 +143,3 @@ 'namespace': '/baz', | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -126,2 +160,3 @@ 'namespace': '/', | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -145,2 +180,3 @@ 'namespace': '/', | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -251,2 +287,33 @@ 'namespace': '/', | ||
| async def test_handle_emit_binary(self): | ||
| with mock.patch.object( | ||
| async_manager.AsyncManager, 'emit' | ||
| ) as super_emit: | ||
| await self.pm._handle_emit({ | ||
| 'event': 'foo', | ||
| 'binary': True, | ||
| 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], | ||
| }) | ||
| super_emit.assert_awaited_once_with( | ||
| 'foo', | ||
| b'bar', | ||
| namespace=None, | ||
| room=None, | ||
| skip_sid=None, | ||
| callback=None, | ||
| ) | ||
| await self.pm._handle_emit({ | ||
| 'event': 'foo', | ||
| 'binary': True, | ||
| 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], | ||
| }) | ||
| super_emit.assert_awaited_with( | ||
| 'foo', | ||
| {'foo': b'bar'}, | ||
| namespace=None, | ||
| room=None, | ||
| skip_sid=None, | ||
| callback=None, | ||
| ) | ||
| async def test_handle_emit_with_namespace(self): | ||
@@ -253,0 +320,0 @@ with mock.patch.object( |
@@ -353,2 +353,58 @@ import logging | ||
| def test_connect_wait_one_namespaces_error(self): | ||
| c = client.Client() | ||
| c.eio.connect = mock.MagicMock() | ||
| c._connect_event = mock.MagicMock() | ||
| def mock_connect(timeout): | ||
| assert timeout == 0.01 | ||
| if c.failed_namespaces == []: | ||
| c.failed_namespaces = ['/foo'] | ||
| return True | ||
| return False | ||
| c._connect_event.wait = mock_connect | ||
| with pytest.raises(exceptions.ConnectionError, | ||
| match='failed to connect: /foo'): | ||
| c.connect( | ||
| 'url', | ||
| namespaces=['/foo'], | ||
| wait=True, | ||
| wait_timeout=0.01, | ||
| ) | ||
| assert c.connected is False | ||
| assert c.namespaces == {} | ||
| assert c.failed_namespaces == ['/foo'] | ||
| def test_connect_wait_three_namespaces_error(self): | ||
| c = client.Client() | ||
| c.eio.connect = mock.MagicMock() | ||
| c._connect_event = mock.MagicMock() | ||
| def mock_connect(timeout): | ||
| assert timeout == 0.01 | ||
| if c.namespaces == {}: | ||
| c.namespaces = {'/bar': '123'} | ||
| return True | ||
| elif c.namespaces == {'/bar': '123'} and c.failed_namespaces == []: | ||
| c.failed_namespaces = ['/baz'] | ||
| return True | ||
| elif c.failed_namespaces == ['/baz']: | ||
| c.failed_namespaces = ['/baz', '/foo'] | ||
| return True | ||
| return False | ||
| c._connect_event.wait = mock_connect | ||
| with pytest.raises(exceptions.ConnectionError, | ||
| match='failed to connect: /baz, /foo'): | ||
| c.connect( | ||
| 'url', | ||
| namespaces=['/foo', '/bar', '/baz'], | ||
| wait=True, | ||
| wait_timeout=0.01, | ||
| ) | ||
| assert c.connected is False | ||
| assert c.namespaces == {'/bar': '123'} | ||
| assert c.failed_namespaces == ['/baz', '/foo'] | ||
| def test_connect_timeout(self): | ||
@@ -355,0 +411,0 @@ c = client.Client() |
@@ -269,14 +269,22 @@ import pytest | ||
| def test_deconstruct_binary(self): | ||
| datas = [b'foo', [b'foo', b'bar'], ['foo', b'bar'], {'foo': b'bar'}, | ||
| {'foo': 'bar', 'baz': b'qux'}, {'foo': [b'bar']}] | ||
| for data in datas: | ||
| bdata, attachments = packet.Packet.deconstruct_binary(data) | ||
| rdata = packet.Packet.reconstruct_binary(bdata, attachments) | ||
| assert data == rdata | ||
| def test_data_is_binary_list(self): | ||
| pkt = packet.Packet() | ||
| 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 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']) | ||
| def test_data_is_binary_dict(self): | ||
| pkt = packet.Packet() | ||
| 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 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'}) |
@@ -72,2 +72,3 @@ import functools | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -82,2 +83,32 @@ 'namespace': '/', | ||
| def test_emit_binary(self): | ||
| self.pm.emit('foo', 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': 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): | ||
@@ -90,2 +121,3 @@ sid = "ferris" | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -106,2 +138,3 @@ 'namespace': '/', | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -122,2 +155,3 @@ 'namespace': '/baz', | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -138,2 +172,3 @@ 'namespace': '/', | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -157,2 +192,3 @@ 'namespace': '/', | ||
| 'event': 'foo', | ||
| 'binary': False, | ||
| 'data': 'bar', | ||
@@ -260,2 +296,31 @@ 'namespace': '/', | ||
| def test_handle_emit_binary(self): | ||
| with mock.patch.object(manager.Manager, 'emit') as super_emit: | ||
| self.pm._handle_emit({ | ||
| 'event': 'foo', | ||
| 'binary': True, | ||
| 'data': [{'_placeholder': True, 'num': 0}, 'YmFy'], | ||
| }) | ||
| super_emit.assert_called_once_with( | ||
| 'foo', | ||
| b'bar', | ||
| namespace=None, | ||
| room=None, | ||
| skip_sid=None, | ||
| callback=None, | ||
| ) | ||
| self.pm._handle_emit({ | ||
| 'event': 'foo', | ||
| 'binary': True, | ||
| 'data': [{'foo': {'_placeholder': True, 'num': 0}}, 'YmFy'], | ||
| }) | ||
| super_emit.assert_called_with( | ||
| 'foo', | ||
| {'foo': b'bar'}, | ||
| namespace=None, | ||
| room=None, | ||
| skip_sid=None, | ||
| callback=None, | ||
| ) | ||
| def test_handle_emit_with_namespace(self): | ||
@@ -262,0 +327,0 @@ with mock.patch.object(manager.Manager, 'emit') as super_emit: |
+2
-1
| [tox] | ||
| envlist=flake8,py{38,39,310,311,312,313},docs | ||
| envlist=flake8,py{38,39,310,311,312,313,314},docs | ||
| skip_missing_interpreters=True | ||
@@ -13,2 +13,3 @@ | ||
| 3.13: py313 | ||
| 3.14: py314 | ||
| pypy-3: pypy3 | ||
@@ -15,0 +16,0 @@ |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
748651
1.35%14334
1.86%