diff --git a/CHANGELOG.md b/CHANGELOG.md index 10a3186bac..0881bb1013 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Standalone client: Improve connection errors. ([#854](https://github.com/aws/glide-for-redis/pull/854)) * Python, Node: When recieving LPOP/RPOP with count, convert result to Array. ([#811](https://github.com/aws/glide-for-redis/pull/811)) * Python: Added TYPE command ([#945](https://github.com/aws/glide-for-redis/pull/945)) +* Python: Added HLEN command ([#944](https://github.com/aws/glide-for-redis/pull/944)) #### Features * Python, Node: Added support in Lua Scripts ([#775](https://github.com/aws/glide-for-redis/pull/775), [#860](https://github.com/aws/glide-for-redis/pull/860)) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index c89654a47f..244aefbb86 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -104,6 +104,7 @@ enum RequestType { ZIncrBy = 66; ZScore = 67; Type = 68; + HLen = 69; } message Command { diff --git a/glide-core/src/socket_listener.rs b/glide-core/src/socket_listener.rs index 7373827113..481a07ec80 100644 --- a/glide-core/src/socket_listener.rs +++ b/glide-core/src/socket_listener.rs @@ -347,6 +347,7 @@ fn get_command(request: &Command) -> Option { RequestType::ZIncrBy => Some(cmd("ZINCRBY")), RequestType::ZScore => Some(cmd("ZSCORE")), RequestType::Type => Some(cmd("TYPE")), + RequestType::HLen => Some(cmd("HLEN")), } } diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index f8a86d6051..752bb5f17f 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -589,6 +589,27 @@ async def hdel(self, key: str, fields: List[str]) -> int: int, await self._execute_command(RequestType.HashDel, [key] + fields) ) + async def hlen(self, key: str) -> int: + """ + Returns the number of fields contained in the hash stored at `key`. + + See https://redis.io/commands/hlen/ for more details. + + Args: + key (str): The key of the hash. + + Returns: + int: The number of fields in the hash, or 0 when the key does not exist. + If `key` holds a value that is not a hash, an error is returned. + + Examples: + >>> await client.hlen("my_hash") + 3 + >>> await client.hlen("non_existing_key") + 0 + """ + return cast(int, await self._execute_command(RequestType.HLen, [key])) + async def lpush(self, key: str, elements: List[str]) -> int: """Insert all the specified values at the head of the list stored at `key`. `elements` are inserted one after the other to the head of the list, from the leftmost element diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 73ea272961..d7b9b5262d 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -366,6 +366,21 @@ def hexists(self, key: str, field: str): """ self.append_command(RequestType.HashExists, [key, field]) + def hlen(self, key: str): + """ + Returns the number of fields contained in the hash stored at `key`. + + See https://redis.io/commands/hlen/ for more details. + + Args: + key (str): The key of the hash. + + Command response: + int: The number of fields in the hash, or 0 when the key does not exist. + If `key` holds a value that is not a hash, the transaction fails with an error. + """ + self.append_command(RequestType.HLen, [key]) + def client_getname(self): """ Get the name of the connection on which the transaction is being executed. diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index 1b87d9f19f..290613e453 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -718,6 +718,25 @@ async def test_hexist(self, redis_client: TRedisClient): assert await redis_client.hexists(key, "nonExistingField") == False assert await redis_client.hexists("nonExistingKey", field2) == False + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_hlen(self, redis_client: TRedisClient): + key = get_random_string(10) + key2 = get_random_string(5) + field = get_random_string(5) + field2 = get_random_string(5) + field_value_map = {field: "value", field2: "value2"} + + assert await redis_client.hset(key, field_value_map) == 2 + assert await redis_client.hlen(key) == 2 + assert await redis_client.hdel(key, [field]) == 1 + assert await redis_client.hlen(key) == 1 + assert await redis_client.hlen("non_existing_hash") == 0 + + assert await redis_client.set(key2, "value") == OK + with pytest.raises(RequestError): + await redis_client.hlen(key2) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_lpush_lpop_lrange(self, redis_client: TRedisClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index 1248053199..35bd6b68c2 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -62,6 +62,7 @@ def transaction_test( transaction.hset(key4, {key: value, key2: value2}) transaction.hget(key4, key2) + transaction.hlen(key4) transaction.hincrby(key4, key3, 5) transaction.hincrbyfloat(key4, key3, 5.5) @@ -116,6 +117,7 @@ def transaction_test( {"timeout": "1000"}, 2, value2, + 2, 5, 10.5, True,