diff --git a/redis/asyncio/connection.py b/redis/asyncio/connection.py index 59f75aa229..09d87be15a 100644 --- a/redis/asyncio/connection.py +++ b/redis/asyncio/connection.py @@ -55,7 +55,7 @@ TimeoutError, ) from redis.typing import EncodableT, EncodedT -from redis.utils import HIREDIS_AVAILABLE, str_if_bytes +from redis.utils import HIREDIS_AVAILABLE, get_lib_version, str_if_bytes hiredis = None if HIREDIS_AVAILABLE: @@ -718,7 +718,14 @@ async def on_connect(self) -> None: await self.send_command("CLIENT", "SETNAME", self.client_name) if str_if_bytes(await self.read_response()) != "OK": raise ConnectionError("Error setting client name") - + if self.is_connected is False: + try: + await self.send_command("CLIENT", "SETINFO", "LIB-NAME", "redis-py") + await self.read_response() + await self.send_command("CLIENT", "SETINFO", "LIB-VERSION", get_lib_version()) + await self.read_response() + except ResponseError: + pass # if a database is specified, switch to it if self.db: await self.send_command("SELECT", self.db) diff --git a/redis/client.py b/redis/client.py index 1a9b96b83d..0cc760e049 100755 --- a/redis/client.py +++ b/redis/client.py @@ -745,6 +745,7 @@ class AbstractRedis: "CLIENT LIST": parse_client_list, "CLIENT INFO": parse_client_info, "CLIENT SETNAME": bool_ok, + "CLIENT SETINFO": bool_ok, "CLIENT UNBLOCK": lambda r: r and int(r) == 1 or False, "CLIENT PAUSE": bool_ok, "CLIENT GETREDIR": int, diff --git a/redis/cluster.py b/redis/cluster.py index 5e6e7da546..746f46d35c 100644 --- a/redis/cluster.py +++ b/redis/cluster.py @@ -212,6 +212,7 @@ class AbstractRedisCluster: "AUTH", "CLIENT LIST", "CLIENT SETNAME", + "CLIENT SETINFO", "CLIENT GETNAME", "CONFIG SET", "CONFIG REWRITE", diff --git a/redis/commands/core.py b/redis/commands/core.py index e2cabb85fa..40617b6756 100644 --- a/redis/commands/core.py +++ b/redis/commands/core.py @@ -706,6 +706,14 @@ def client_setname(self, name: str, **kwargs) -> ResponseT: """ return self.execute_command("CLIENT SETNAME", name, **kwargs) + def client_setinfo(self, attr: str, value: str, **kwargs) -> ResponseT: + """ + Sets the current connection libname or version + + For mor information see https://redis.io/commands/client-setinfo + """ + return self.execute_command("CLIENT SETINFO", attr, value, **kwargs) + def client_unblock( self, client_id: int, error: bool = False, **kwargs ) -> ResponseT: diff --git a/redis/connection.py b/redis/connection.py index eefdd96523..8f3d2d7909 100644 --- a/redis/connection.py +++ b/redis/connection.py @@ -38,6 +38,7 @@ CRYPTOGRAPHY_AVAILABLE, HIREDIS_AVAILABLE, HIREDIS_PACK_AVAILABLE, + get_lib_version, str_if_bytes, ) @@ -739,6 +740,10 @@ def _error_message(self, exception): def on_connect(self): "Initialize the connection, authenticate and select a database" self._parser.on_connect(self) + if ssl_available: + authened = False + else: + authened = True # if credential provider or username and/or password are set, authenticate if self.credential_provider or (self.username or self.password): @@ -763,12 +768,22 @@ def on_connect(self): if str_if_bytes(auth_response) != "OK": raise AuthenticationError("Invalid Username or Password") + else: + authened = True # if a client_name is given, set it if self.client_name: self.send_command("CLIENT", "SETNAME", self.client_name) if str_if_bytes(self.read_response()) != "OK": raise ConnectionError("Error setting client name") + if authened is True: + try: + self.send_command("CLIENT", "SETINFO", "LIB-NAME", "redis-py") + self.read_response() + self.send_command("CLIENT", "SETINFO", "LIB-VERSION", get_lib_version()) + self.read_response() + except ResponseError: + pass # if a database is specified, switch to it if self.db: diff --git a/redis/utils.py b/redis/utils.py index d95e62c042..c540357192 100644 --- a/redis/utils.py +++ b/redis/utils.py @@ -1,3 +1,4 @@ +import sys from contextlib import contextmanager from functools import wraps from typing import Any, Dict, Mapping, Union @@ -19,6 +20,11 @@ except ImportError: CRYPTOGRAPHY_AVAILABLE = False +if sys.version_info >= (3, 8): + from importlib import metadata +else: + import importlib_metadata as metadata + def from_url(url, **kwargs): """ @@ -110,3 +116,11 @@ def wrapper(*args, **kwargs): return wrapper return decorator + + +def get_lib_version(): + try: + libver = metadata.version("redis") + except metadata.PackageNotFoundError: + libver = "99.99.99" + return libver diff --git a/tests/test_asyncio/test_cluster.py b/tests/test_asyncio/test_cluster.py index 13e5e26ae3..13278478dc 100644 --- a/tests/test_asyncio/test_cluster.py +++ b/tests/test_asyncio/test_cluster.py @@ -870,6 +870,15 @@ async def test_client_setname(self, r: RedisCluster) -> None: client_name = await r.client_getname(target_nodes=node) assert client_name == "redis_py_test" + @skip_if_server_version_lt("7.2.0") + async def test_client_setinfo(self, r: RedisCluster) -> None: + node = r.get_random_node() + await r.client_setinfo("lib-name", "redis-py", target_nodes=node) + await r.client_setinfo("lib-ver", "4.33", target_nodes=node) + info = await r.client_info(target_nodes=node) + assert "lib-name" in info + assert "lib-ver" in info + async def test_exists(self, r: RedisCluster) -> None: d = {"a": b"1", "b": b"2", "c": b"3", "d": b"4"} await r.mset_nonatomic(d) diff --git a/tests/test_asyncio/test_commands.py b/tests/test_asyncio/test_commands.py index 7c6fd45ab9..6211d487b0 100644 --- a/tests/test_asyncio/test_commands.py +++ b/tests/test_asyncio/test_commands.py @@ -340,6 +340,15 @@ async def test_client_setname(self, r: redis.Redis): assert await r.client_setname("redis_py_test") assert await r.client_getname() == "redis_py_test" + @skip_if_server_version_lt("7.2.0") + @pytest.mark.onlynoncluster + async def test_client_setinfo(self, r: redis.Redis): + assert await r.client_setinfo("lib-name", "redis-py") + assert await r.client_setinfo("lib-ver", "4.33") + info = await r.client_info() + assert "lib-name" in info + assert "lib-ver" in info + @skip_if_server_version_lt("2.6.9") @pytest.mark.onlynoncluster async def test_client_kill(self, r: redis.Redis, r2): @@ -438,6 +447,14 @@ async def test_client_list_after_client_setname(self, r: redis.Redis): # we don't know which client ours will be assert "redis_py_test" in [c["name"] for c in clients] + @skip_if_server_version_lt("7.2.0") + async def test_client_list_after_client_setinfo(self, r: redis.Redis): + await r.client_setinfo("lib-name", "redis-py") + await r.client_setinfo("lib-ver", "4.33") + clients = await r.client_list() + assert "redis-py" in [c["lib-name"] for c in clients] + assert "4.33" in [c["lib-ver"] for c in clients] + @skip_if_server_version_lt("2.9.50") @pytest.mark.onlynoncluster async def test_client_pause(self, r: redis.Redis): diff --git a/tests/test_commands.py b/tests/test_commands.py index 94249e9419..6243d98897 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -377,7 +377,7 @@ def teardown(): user_client.hset("cache:0", "hkey", "hval") assert isinstance(r.acl_log(), list) - assert len(r.acl_log()) == 2 + assert len(r.acl_log()) == 3 assert len(r.acl_log(count=1)) == 1 assert isinstance(r.acl_log()[0], dict) assert "client-info" in r.acl_log(count=1)[0] @@ -530,6 +530,15 @@ def test_client_setname(self, r): assert r.client_setname("redis_py_test") assert r.client_getname() == "redis_py_test" + @pytest.mark.onlynoncluster + @skip_if_server_version_lt("7.2.0") + def test_client_setinfo(self, r): + assert r.client_setinfo("lib-name", "redis-py") + assert r.client_setinfo("lib-ver", "4.33") + info = r.client_info() + assert "lib-name" in info + assert "lib-ver" in info + @pytest.mark.onlynoncluster @skip_if_server_version_lt("2.6.9") def test_client_kill(self, r, r2): @@ -628,6 +637,14 @@ def test_client_list_after_client_setname(self, r): # we don't know which client ours will be assert "redis_py_test" in [c["name"] for c in clients] + @skip_if_server_version_lt("7.2.0") + def test_client_list_after_client_setinfo(self, r): + r.client_setinfo("lib-name", "redis-py") + r.client_setinfo("lib-ver", "4.33") + clients = r.client_list() + assert "redis-py" in [c["lib-name"] for c in clients] + assert "4.33" in [c["lib-ver"] for c in clients] + @skip_if_server_version_lt("6.2.0") def test_client_kill_filter_by_laddr(self, r, r2): r.client_setname("redis-py-c1")