python-socketio
Advanced tools
| import pytest | ||
| from socketio.redis_manager import parse_redis_sentinel_url | ||
| class TestPubSubManager: | ||
| def test_sentinel_url_parser(self): | ||
| with pytest.raises(ValueError): | ||
| parse_redis_sentinel_url('redis://localhost:6379/0') | ||
| assert parse_redis_sentinel_url( | ||
| 'redis+sentinel://localhost:6379' | ||
| ) == ( | ||
| [('localhost', 6379)], | ||
| None, | ||
| {} | ||
| ) | ||
| assert parse_redis_sentinel_url( | ||
| 'redis+sentinel://192.168.0.1:6379,192.168.0.2:6379/' | ||
| ) == ( | ||
| [('192.168.0.1', 6379), ('192.168.0.2', 6379)], | ||
| None, | ||
| {} | ||
| ) | ||
| assert parse_redis_sentinel_url( | ||
| 'redis+sentinel://h1:6379,h2:6379/0' | ||
| ) == ( | ||
| [('h1', 6379), ('h2', 6379)], | ||
| None, | ||
| {'db': 0} | ||
| ) | ||
| assert parse_redis_sentinel_url( | ||
| 'redis+sentinel://user:password@h1:6379,h2:6379,h1:6380/0/myredis' | ||
| ) == ( | ||
| [('h1', 6379), ('h2', 6379), ('h1', 6380)], | ||
| 'myredis', | ||
| {'username': 'user', 'password': 'password', 'db': 0} | ||
| ) |
+0
-6
@@ -22,8 +22,2 @@ The Socket.IO Server | ||
| If you plan to build an asynchronous web server based on the ``asyncio`` | ||
| package, then you can install this package and some additional dependencies | ||
| that are needed with:: | ||
| pip install "python-socketio[asyncio]" | ||
| Creating a Server Instance | ||
@@ -30,0 +24,0 @@ -------------------------- |
+3
-2
@@ -1,4 +0,4 @@ | ||
| Metadata-Version: 2.1 | ||
| Metadata-Version: 2.4 | ||
| Name: python-socketio | ||
| Version: 5.12.1 | ||
| Version: 5.13.0 | ||
| Summary: Socket.IO server and client for Python | ||
@@ -25,2 +25,3 @@ Author-email: Miguel Grinberg <miguel.grinberg@gmail.com> | ||
| Requires-Dist: sphinx; extra == "docs" | ||
| Dynamic: license-file | ||
@@ -27,0 +28,0 @@ python-socketio |
+1
-1
| [project] | ||
| name = "python-socketio" | ||
| version = "5.12.1" | ||
| version = "5.13.0" | ||
| authors = [ | ||
@@ -5,0 +5,0 @@ { name = "Miguel Grinberg", email = "miguel.grinberg@gmail.com" }, |
@@ -1,4 +0,4 @@ | ||
| Metadata-Version: 2.1 | ||
| Metadata-Version: 2.4 | ||
| Name: python-socketio | ||
| Version: 5.12.1 | ||
| Version: 5.13.0 | ||
| Summary: Socket.IO server and client for Python | ||
@@ -25,2 +25,3 @@ Author-email: Miguel Grinberg <miguel.grinberg@gmail.com> | ||
| Requires-Dist: sphinx; extra == "docs" | ||
| Dynamic: license-file | ||
@@ -27,0 +28,0 @@ python-socketio |
@@ -73,2 +73,3 @@ LICENSE | ||
| tests/common/test_pubsub_manager.py | ||
| tests/common/test_redis_manager.py | ||
| tests/common/test_server.py | ||
@@ -75,0 +76,0 @@ tests/common/test_simple_client.py |
@@ -161,3 +161,3 @@ import asyncio | ||
| return | ||
| raise exceptions.ConnectionError(exc.args[0]) from None | ||
| raise exceptions.ConnectionError(exc.args[0]) from exc | ||
@@ -195,2 +195,3 @@ if wait: | ||
| # connected while sleeping above | ||
| print('oops') | ||
| continue | ||
@@ -329,3 +330,3 @@ break | ||
| namespace=n)) | ||
| await self.eio.disconnect(abort=True) | ||
| await self.eio.disconnect() | ||
@@ -332,0 +333,0 @@ async def shutdown(self): |
@@ -16,2 +16,3 @@ import asyncio | ||
| from .async_pubsub_manager import AsyncPubSubManager | ||
| from .redis_manager import parse_redis_sentinel_url | ||
@@ -33,4 +34,7 @@ | ||
| :param url: The connection URL for the Redis server. For a default Redis | ||
| store running on the same host, use ``redis://``. To use an | ||
| SSL connection, use ``rediss://``. | ||
| store running on the same host, use ``redis://``. To use a | ||
| TLS connection, use ``rediss://``. To use Redis Sentinel, use | ||
| ``redis+sentinel://`` with a comma-separated list of hosts | ||
| and the service name after the db in the URL path. Example: | ||
| ``redis+sentinel://user:pw@host1:1234,host2:2345/0/myredis``. | ||
| :param channel: The channel name on which the server sends and receives | ||
@@ -42,3 +46,3 @@ notifications. Must be the same in all the servers. | ||
| :param redis_options: additional keyword arguments to be passed to | ||
| ``aioredis.from_url()``. | ||
| ``Redis.from_url()`` or ``Sentinel()``. | ||
| """ | ||
@@ -60,4 +64,12 @@ name = 'aioredis' | ||
| def _redis_connect(self): | ||
| self.redis = aioredis.Redis.from_url(self.redis_url, | ||
| **self.redis_options) | ||
| if not self.redis_url.startswith('redis+sentinel://'): | ||
| self.redis = aioredis.Redis.from_url(self.redis_url, | ||
| **self.redis_options) | ||
| else: | ||
| sentinels, service_name, connection_kwargs = \ | ||
| parse_redis_sentinel_url(self.redis_url) | ||
| kwargs = self.redis_options | ||
| kwargs.update(connection_kwargs) | ||
| sentinel = aioredis.sentinel.Sentinel(sentinels, **kwargs) | ||
| self.redis = sentinel.master_for(service_name or self.channel) | ||
| self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True) | ||
@@ -64,0 +76,0 @@ |
@@ -15,2 +15,4 @@ import asyncio | ||
| """ | ||
| client_class = AsyncClient | ||
| def __init__(self, *args, **kwargs): | ||
@@ -64,3 +66,4 @@ self.client_args = args | ||
| self.input_event.clear() | ||
| self.client = AsyncClient(*self.client_args, **self.client_kwargs) | ||
| self.client = self.client_class( | ||
| *self.client_args, **self.client_kwargs) | ||
@@ -67,0 +70,0 @@ @self.client.event(namespace=self.namespace) |
@@ -159,3 +159,3 @@ import random | ||
| return | ||
| raise exceptions.ConnectionError(exc.args[0]) from None | ||
| raise exceptions.ConnectionError(exc.args[0]) from exc | ||
@@ -310,3 +310,3 @@ if wait: | ||
| packet.DISCONNECT, namespace=n)) | ||
| self.eio.disconnect(abort=True) | ||
| self.eio.disconnect() | ||
@@ -313,0 +313,0 @@ def shutdown(self): |
| import logging | ||
| import pickle | ||
| import time | ||
| from urllib.parse import urlparse | ||
@@ -15,2 +16,28 @@ try: | ||
| def parse_redis_sentinel_url(url): | ||
| """Parse a Redis Sentinel URL with the format: | ||
| redis+sentinel://[:password]@host1:port1,host2:port2,.../db/service_name | ||
| """ | ||
| parsed_url = urlparse(url) | ||
| if parsed_url.scheme != 'redis+sentinel': | ||
| raise ValueError('Invalid Redis Sentinel URL') | ||
| sentinels = [] | ||
| for host_port in parsed_url.netloc.split('@')[-1].split(','): | ||
| host, port = host_port.rsplit(':', 1) | ||
| sentinels.append((host, int(port))) | ||
| kwargs = {} | ||
| if parsed_url.username: | ||
| kwargs['username'] = parsed_url.username | ||
| if parsed_url.password: | ||
| kwargs['password'] = parsed_url.password | ||
| service_name = None | ||
| if parsed_url.path: | ||
| parts = parsed_url.path.split('/') | ||
| if len(parts) >= 2 and parts[1] != '': | ||
| kwargs['db'] = int(parts[1]) | ||
| if len(parts) >= 3 and parts[2] != '': | ||
| service_name = parts[2] | ||
| return sentinels, service_name, kwargs | ||
| class RedisManager(PubSubManager): # pragma: no cover | ||
@@ -31,4 +58,7 @@ """Redis based client manager. | ||
| :param url: The connection URL for the Redis server. For a default Redis | ||
| store running on the same host, use ``redis://``. To use an | ||
| SSL connection, use ``rediss://``. | ||
| store running on the same host, use ``redis://``. To use a | ||
| TLS connection, use ``rediss://``. To use Redis Sentinel, use | ||
| ``redis+sentinel://`` with a comma-separated list of hosts | ||
| and the service name after the db in the URL path. Example: | ||
| ``redis+sentinel://user:pw@host1:1234,host2:2345/0/myredis``. | ||
| :param channel: The channel name on which the server sends and receives | ||
@@ -40,3 +70,3 @@ notifications. Must be the same in all the servers. | ||
| :param redis_options: additional keyword arguments to be passed to | ||
| ``Redis.from_url()``. | ||
| ``Redis.from_url()`` or ``Sentinel()``. | ||
| """ | ||
@@ -72,4 +102,12 @@ name = 'redis' | ||
| def _redis_connect(self): | ||
| self.redis = redis.Redis.from_url(self.redis_url, | ||
| **self.redis_options) | ||
| if not self.redis_url.startswith('redis+sentinel://'): | ||
| self.redis = redis.Redis.from_url(self.redis_url, | ||
| **self.redis_options) | ||
| else: | ||
| sentinels, service_name, connection_kwargs = \ | ||
| parse_redis_sentinel_url(self.redis_url) | ||
| kwargs = self.redis_options | ||
| kwargs.update(connection_kwargs) | ||
| sentinel = redis.sentinel.Sentinel(sentinels, **kwargs) | ||
| self.redis = sentinel.master_for(service_name or self.channel) | ||
| self.pubsub = self.redis.pubsub(ignore_subscribe_messages=True) | ||
@@ -76,0 +114,0 @@ |
@@ -15,2 +15,4 @@ from threading import Event | ||
| """ | ||
| client_class = Client | ||
| def __init__(self, *args, **kwargs): | ||
@@ -62,3 +64,4 @@ self.client_args = args | ||
| self.input_event.clear() | ||
| self.client = Client(*self.client_args, **self.client_kwargs) | ||
| self.client = self.client_class( | ||
| *self.client_args, **self.client_kwargs) | ||
@@ -65,0 +68,0 @@ @self.client.event(namespace=self.namespace) |
@@ -478,3 +478,3 @@ import asyncio | ||
| ) | ||
| c.eio.disconnect.assert_awaited_once_with(abort=True) | ||
| c.eio.disconnect.assert_awaited_once_with() | ||
@@ -997,3 +997,3 @@ async def test_disconnect_namespaces(self): | ||
| ) | ||
| c.eio.disconnect.assert_awaited_once_with(abort=True) | ||
| c.eio.disconnect.assert_awaited_once_with() | ||
@@ -1000,0 +1000,0 @@ async def test_shutdown_disconnect_namespaces(self): |
@@ -19,10 +19,35 @@ import asyncio | ||
| async def test_connect(self): | ||
| mock_client = mock.MagicMock() | ||
| original_client_class = AsyncSimpleClient.client_class | ||
| AsyncSimpleClient.client_class = mock_client | ||
| client = AsyncSimpleClient(123, a='b') | ||
| with mock.patch('socketio.async_simple_client.AsyncClient') \ | ||
| as mock_client: | ||
| mock_client.return_value.connect = mock.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') | ||
| assert client.client == mock_client() | ||
| mock_client().connect.assert_awaited_once_with( | ||
| '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' | ||
| assert not client.input_event.is_set() | ||
| AsyncSimpleClient.client_class = original_client_class | ||
| async def test_connect_context_manager(self): | ||
| mock_client = mock.MagicMock() | ||
| original_client_class = AsyncSimpleClient.client_class | ||
| AsyncSimpleClient.client_class = mock_client | ||
| async with AsyncSimpleClient(123, a='b') as client: | ||
| mock_client.return_value.connect = mock.AsyncMock() | ||
| await client.connect('url', headers='h', auth='a', transports='t', | ||
| namespace='n', socketio_path='s', | ||
| wait_timeout='w') | ||
| 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') | ||
@@ -34,29 +59,9 @@ assert client.client == mock_client() | ||
| mock_client().event.call_count == 3 | ||
| mock_client().on.assert_called_once_with('*', namespace='n') | ||
| mock_client().on.assert_called_once_with( | ||
| '*', namespace='n') | ||
| assert client.namespace == 'n' | ||
| assert not client.input_event.is_set() | ||
| async 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: | ||
| mock_client.return_value.connect = mock.AsyncMock() | ||
| AsyncSimpleClient.client_class = original_client_class | ||
| 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.assert_awaited_once_with( | ||
| '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' | ||
| assert not client.input_event.is_set() | ||
| await _t() | ||
| async def test_connect_twice(self): | ||
@@ -63,0 +68,0 @@ client = AsyncSimpleClient(123, a='b') |
@@ -636,3 +636,3 @@ import logging | ||
| ) | ||
| c.eio.disconnect.assert_called_once_with(abort=True) | ||
| c.eio.disconnect.assert_called_once_with() | ||
@@ -1142,3 +1142,3 @@ def test_disconnect_namespaces(self): | ||
| ) | ||
| c.eio.disconnect.assert_called_once_with(abort=True) | ||
| c.eio.disconnect.assert_called_once_with() | ||
@@ -1145,0 +1145,0 @@ def test_shutdown_disconnect_namespaces(self): |
@@ -17,6 +17,30 @@ from unittest import mock | ||
| def test_connect(self): | ||
| mock_client = mock.MagicMock() | ||
| original_client_class = SimpleClient.client_class | ||
| SimpleClient.client_class = mock_client | ||
| 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') | ||
| mock_client().event.call_count == 3 | ||
| mock_client().on.assert_called_once_with('*', namespace='n') | ||
| assert client.namespace == 'n' | ||
| assert not client.input_event.is_set() | ||
| SimpleClient.client_class = original_client_class | ||
| def test_connect_context_manager(self): | ||
| mock_client = mock.MagicMock() | ||
| original_client_class = SimpleClient.client_class | ||
| SimpleClient.client_class = mock_client | ||
| with SimpleClient(123, a='b') as client: | ||
| client.connect('url', headers='h', auth='a', transports='t', | ||
| namespace='n', socketio_path='s', wait_timeout='w') | ||
| namespace='n', socketio_path='s', | ||
| wait_timeout='w') | ||
| mock_client.assert_called_once_with(123, a='b') | ||
@@ -32,17 +56,3 @@ assert client.client == mock_client() | ||
| 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') | ||
| 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') | ||
| mock_client().event.call_count == 3 | ||
| mock_client().on.assert_called_once_with('*', namespace='n') | ||
| assert client.namespace == 'n' | ||
| assert not client.input_event.is_set() | ||
| SimpleClient.client_class = original_client_class | ||
@@ -49,0 +59,0 @@ def test_connect_twice(self): |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
724928
0.51%86
1.18%13803
0.69%