diff --git a/CHANGELOG.md b/CHANGELOG.md index bb59b8dcac..2d645b05a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Python: Added RENAME command ([#1252](https://github.com/aws/glide-for-redis/pull/1252)) * Python: Added APPEND command ([#1152](https://github.com/aws/glide-for-redis/pull/1152)) * Python: Added GEOADD command ([#1259](https://github.com/aws/glide-for-redis/pull/1259)) +* Python: Added GEOHASH command ([#1281](https://github.com/aws/glide-for-redis/pull/1281)) #### Fixes * Python: Fix typing error "‘type’ object is not subscriptable" ([#1203](https://github.com/aws/glide-for-redis/pull/1203)) diff --git a/glide-core/src/protobuf/redis_request.proto b/glide-core/src/protobuf/redis_request.proto index 00498119e1..e602c914e7 100644 --- a/glide-core/src/protobuf/redis_request.proto +++ b/glide-core/src/protobuf/redis_request.proto @@ -161,6 +161,7 @@ enum RequestType { SMIsMember = 118; LastSave = 120; GeoAdd = 121; + GeoHash = 122; } message Command { diff --git a/glide-core/src/request_type.rs b/glide-core/src/request_type.rs index 6bd14cd2f6..3f0a83045d 100644 --- a/glide-core/src/request_type.rs +++ b/glide-core/src/request_type.rs @@ -129,6 +129,7 @@ pub enum RequestType { SMIsMember = 118, LastSave = 120, GeoAdd = 121, + GeoHash = 122, } fn get_two_word_command(first: &str, second: &str) -> Cmd { @@ -261,6 +262,7 @@ impl From<::protobuf::EnumOrUnknown> for RequestType { ProtobufRequestType::SMIsMember => RequestType::SMIsMember, ProtobufRequestType::LastSave => RequestType::LastSave, ProtobufRequestType::GeoAdd => RequestType::GeoAdd, + ProtobufRequestType::GeoHash => RequestType::GeoHash, } } } @@ -389,6 +391,7 @@ impl RequestType { RequestType::SMIsMember => Some(cmd("SMISMEMBER")), RequestType::LastSave => Some(cmd("LASTSAVE")), RequestType::GeoAdd => Some(cmd("GEOADD")), + RequestType::GeoHash => Some(cmd("GEOHASH")), } } } diff --git a/python/python/glide/async_commands/core.py b/python/python/glide/async_commands/core.py index 39bc1c2604..551dd0d264 100644 --- a/python/python/glide/async_commands/core.py +++ b/python/python/glide/async_commands/core.py @@ -1590,6 +1590,31 @@ async def geoadd( await self._execute_command(RequestType.GeoAdd, args), ) + async def geohash(self, key: str, members: List[str]) -> List[Optional[str]]: + """ + Returns the GeoHash strings representing the positions of all the specified members in the sorted set stored at + `key`. + + See https://valkey.io/commands/geohash for more details. + + Args: + key (str): The key of the sorted set. + members (List[str]): The list of members whose GeoHash strings are to be retrieved. + + Returns: + List[Optional[str]]: A list of GeoHash strings representing the positions of the specified members stored at `key`. + If a member does not exist in the sorted set, a None value is returned for that member. + + Examples: + >>> await client.geoadd("my_geo_sorted_set", {"Palermo": GeospatialData(13.361389, 38.115556), "Catania": GeospatialData(15.087269, 37.502669)}) + >>> await client.geohash("my_geo_sorted_set", ["Palermo", "Catania", "some city]) + ["sqc8b49rny0", "sqdtr74hyu0", None] # Indicates the GeoHash strings for the specified members. + """ + return cast( + List[Optional[str]], + await self._execute_command(RequestType.GeoHash, [key] + members), + ) + async def zadd( self, key: str, diff --git a/python/python/glide/async_commands/transaction.py b/python/python/glide/async_commands/transaction.py index 7445966e10..e6548903e9 100644 --- a/python/python/glide/async_commands/transaction.py +++ b/python/python/glide/async_commands/transaction.py @@ -1207,6 +1207,23 @@ def geoadd( return self.append_command(RequestType.GeoAdd, args) + def geohash(self: TTransaction, key: str, members: List[str]) -> TTransaction: + """ + Returns the GeoHash strings representing the positions of all the specified members in the sorted set stored at + `key`. + + See https://valkey.io/commands/geohash for more details. + + Args: + key (str): The key of the sorted set. + members (List[str]): The list of members whose GeoHash strings are to be retrieved. + + Commands response: + List[Optional[str]]: A list of GeoHash strings representing the positions of the specified members stored at `key`. + If a member does not exist in the sorted set, a None value is returned for that member. + """ + return self.append_command(RequestType.GeoHash, [key] + members) + def zadd( self: TTransaction, key: str, diff --git a/python/python/tests/test_async_client.py b/python/python/tests/test_async_client.py index a6fe0dda62..bc670a4c2f 100644 --- a/python/python/tests/test_async_client.py +++ b/python/python/tests/test_async_client.py @@ -1275,6 +1275,37 @@ async def test_geoadd_invalid_args(self, redis_client: TRedisClient): with pytest.raises(RequestError): await redis_client.geoadd(key, {"Place": GeospatialData(0, -86)}) + @pytest.mark.parametrize("cluster_mode", [True, False]) + @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) + async def test_geohash(self, redis_client: TRedisClient): + key = get_random_string(10) + members_coordinates = { + "Palermo": GeospatialData(13.361389, 38.115556), + "Catania": GeospatialData(15.087269, 37.502669), + } + assert await redis_client.geoadd(key, members_coordinates) == 2 + assert await redis_client.geohash(key, ["Palermo", "Catania", "Place"]) == [ + "sqc8b49rny0", + "sqdtr74hyu0", + None, + ] + + assert ( + await redis_client.geohash( + "non_existing_key", ["Palermo", "Catania", "Place"] + ) + == [None] * 3 + ) + + # Neccessary to check since we are enforcing the user to pass a list of members while redis don't + # But when running the command with key only (and no members) the returned value will always be an empty list + # So in case of any changes, this test will fail and inform us that we should allow not passing any members. + assert await redis_client.geohash(key, []) == [] + + assert await redis_client.set(key, "value") == OK + with pytest.raises(RequestError): + await redis_client.geohash(key, ["Palermo", "Catania"]) + @pytest.mark.parametrize("cluster_mode", [True, False]) @pytest.mark.parametrize("protocol", [ProtocolVersion.RESP2, ProtocolVersion.RESP3]) async def test_zadd_zaddincr(self, redis_client: TRedisClient): diff --git a/python/python/tests/test_transaction.py b/python/python/tests/test_transaction.py index ca60e05779..62fee669b3 100644 --- a/python/python/tests/test_transaction.py +++ b/python/python/tests/test_transaction.py @@ -208,6 +208,8 @@ async def transaction_test( }, ) args.append(2) + transaction.geohash(key9, ["Palermo", "Catania", "Place"]) + args.append(["sqc8b49rny0", "sqdtr74hyu0", None]) return args