Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python: adds GEODIST command #1260

Merged
merged 3 commits into from
Apr 24, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 GEODIST command ([#1260](https://github.com/aws/glide-for-redis/pull/1260))
* Python: Added GEOHASH command ([#1281](https://github.com/aws/glide-for-redis/pull/1281))
* Python: Added ZLEXCOUNT command ([#1305](https://github.com/aws/glide-for-redis/pull/1305))
* Python: Added ZREMRANGEBYLEX command ([#1306](https://github.com/aws/glide-for-redis/pull/1306))
Expand Down
2 changes: 1 addition & 1 deletion glide-core/src/client/value_conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ pub(crate) fn expected_type_for_cmd(cmd: &Cmd) -> Option<ExpectedReturnType> {
| b"SISMEMBER" | b"PERSIST" | b"SMOVE" => Some(ExpectedReturnType::Boolean),
b"SMISMEMBER" => Some(ExpectedReturnType::ArrayOfBools),
b"SMEMBERS" | b"SINTER" => Some(ExpectedReturnType::Set),
b"ZSCORE" => Some(ExpectedReturnType::DoubleOrNull),
b"ZSCORE" | b"GEODIST" => Some(ExpectedReturnType::DoubleOrNull),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to add a test?

b"ZPOPMIN" | b"ZPOPMAX" => Some(ExpectedReturnType::MapOfStringToDouble),
b"JSON.TOGGLE" => Some(ExpectedReturnType::JsonToggleReturnType),
b"ZADD" => cmd
Expand Down
1 change: 1 addition & 0 deletions glide-core/src/protobuf/redis_request.proto
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ enum RequestType {
GeoAdd = 121;
GeoHash = 122;
ObjectEncoding = 123;
GeoDist = 124;
}

message Command {
Expand Down
3 changes: 3 additions & 0 deletions glide-core/src/request_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ pub enum RequestType {
GeoAdd = 121,
GeoHash = 122,
ObjectEncoding = 123,
GeoDist = 124,
}

fn get_two_word_command(first: &str, second: &str) -> Cmd {
Expand Down Expand Up @@ -265,6 +266,7 @@ impl From<::protobuf::EnumOrUnknown<ProtobufRequestType>> for RequestType {
ProtobufRequestType::GeoAdd => RequestType::GeoAdd,
ProtobufRequestType::GeoHash => RequestType::GeoHash,
ProtobufRequestType::ObjectEncoding => RequestType::ObjectEncoding,
ProtobufRequestType::GeoDist => RequestType::GeoDist,
}
}
}
Expand Down Expand Up @@ -395,6 +397,7 @@ impl RequestType {
RequestType::GeoAdd => Some(cmd("GEOADD")),
RequestType::GeoHash => Some(cmd("GEOHASH")),
RequestType::ObjectEncoding => Some(get_two_word_command("OBJECT", "ENCODING")),
RequestType::GeoDist => Some(cmd("GEODIST")),
}
}
}
4 changes: 3 additions & 1 deletion python/python/glide/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
ExpirySet,
ExpiryType,
GeospatialData,
GeoUnit,
InfoSection,
UpdateOptions,
)
Expand Down Expand Up @@ -57,10 +58,11 @@
"RedisClientConfiguration",
"ScoreBoundary",
"ConditionalChange",
"GeospatialData",
"ExpireOptions",
"ExpirySet",
"ExpiryType",
"GeoUnit",
"GeospatialData",
"InfBound",
"InfoSection",
"json",
Expand Down
64 changes: 64 additions & 0 deletions python/python/glide/async_commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,29 @@ def __init__(self, longitude: float, latitude: float):
self.latitude = latitude


class GeoUnit(Enum):
"""
Enumeration representing distance units options for the `GEODIST` command.
"""

METERS = "m"
"""
Represents distance in meters.
"""
KILOMETERS = "km"
"""
Represents distance in kilometers.
"""
MILES = "mi"
"""
Represents distance in miles.
"""
FEET = "ft"
"""
Represents distance in feet.
"""


class ExpirySet:
"""SET option: Represents the expiry type and value to be executed with "SET" command."""

Expand Down Expand Up @@ -1627,6 +1650,47 @@ async def geoadd(
await self._execute_command(RequestType.GeoAdd, args),
)

async def geodist(
self,
key: str,
member1: str,
member2: str,
unit: Optional[GeoUnit] = None,
) -> Optional[float]:
"""
Returns the distance between two members in the geospatial index stored at `key`.

See https://valkey.io/commands/geodist for more details.

Args:
key (str): The key of the sorted set.
member1 (str): The name of the first member.
member2 (str): The name of the second member.
unit (Optional[GeoUnit]): The unit of distance measurement. See `GeoUnit`.
If not specified, the default unit is `METERS`.

Returns:
Optional[float]: The distance between `member1` and `member2`.
If one or both members do not exist, or if the key does not exist, returns None.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If one or both members do not exist, or if the key does not exist, returns None.
If one or both members do not exist, or if the key does not exist, returns `None`.


Examples:
>>> await client.geoadd("my_geo_set", {"Palermo": GeospatialData(13.361389, 38.115556), "Catania": GeospatialData(15.087269, 37.502669)})
>>> await client.geodist("my_geo_set", "Palermo", "Catania")
166274.1516 # Indicates the distance between "Palermo" and "Catania" in meters.
>>> await client.geodist("my_geo_set", "Palermo", "Palermo", unit=GeoUnit.KILOMETERS)
166.2742 # Indicates the distance between "Palermo" and "Palermo" in kilometers.
>>> await client.geodist("my_geo_set", "non-existing", "Palermo", unit=GeoUnit.KILOMETERS)
None # Returns None for non-existing memeber.
shohamazon marked this conversation as resolved.
Show resolved Hide resolved
"""
args = [key, member1, member2]
if unit:
args.append(unit.value)

return cast(
Optional[float],
await self._execute_command(RequestType.GeoDist, 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
Expand Down
30 changes: 30 additions & 0 deletions python/python/glide/async_commands/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
ExpireOptions,
ExpirySet,
GeospatialData,
GeoUnit,
InfoSection,
InsertPosition,
UpdateOptions,
Expand Down Expand Up @@ -1233,6 +1234,35 @@ def geoadd(

return self.append_command(RequestType.GeoAdd, args)

def geodist(
self: TTransaction,
key: str,
member1: str,
member2: str,
unit: Optional[GeoUnit] = None,
) -> TTransaction:
"""
Returns the distance between two members in the geospatial index stored at `key`.

See https://valkey.io/commands/geodist for more details.

Args:
key (str): The key of the sorted set.
member1 (str): The name of the first member.
member2 (str): The name of the second member.
unit (Optional[GeoUnit]): The unit of distance measurement. See `GeoUnit`.
If not specified, the default unit is meters.

Returns:
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
shohamazon marked this conversation as resolved.
Show resolved Hide resolved
Optional[float]: The distance between `member1` and `member2`.
If one or both members do not exist, or if the key does not exist, returns None.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
If one or both members do not exist, or if the key does not exist, returns None.
If one or both members do not exist, or if the key does not exist, returns `None`.

"""
args = [key, member1, member2]
if unit:
args.append(unit.value)

return self.append_command(RequestType.GeoDist, 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
Expand Down
28 changes: 28 additions & 0 deletions python/python/tests/test_async_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
ExpirySet,
ExpiryType,
GeospatialData,
GeoUnit,
InfBound,
InfoSection,
InsertPosition,
Expand Down Expand Up @@ -1338,6 +1339,33 @@ async def test_geohash(self, redis_client: TRedisClient):
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_geodist(self, redis_client: TRedisClient):
key, key2 = get_random_string(10), 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.geodist(key, "Palermo", "Catania") == 166274.1516
assert (
await redis_client.geodist(key, "Palermo", "Catania", GeoUnit.KILOMETERS)
== 166.2742
)
assert await redis_client.geodist(key, "Palermo", "Palermo", GeoUnit.MILES) == 0
assert (
Yury-Fridlyand marked this conversation as resolved.
Show resolved Hide resolved
await redis_client.geodist(
key, "Palermo", "non-existing-member", GeoUnit.FEET
)
== None
)

assert await redis_client.set(key2, "value") == OK
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment for that test

with pytest.raises(RequestError):
await redis_client.geodist(key2, "Palmero", "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):
Expand Down
2 changes: 2 additions & 0 deletions python/python/tests/test_transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,8 @@ async def transaction_test(
},
)
args.append(2)
transaction.geodist(key9, "Palermo", "Catania")
args.append(166274.1516)
transaction.geohash(key9, ["Palermo", "Catania", "Place"])
args.append(["sqc8b49rny0", "sqdtr74hyu0", None])
return args
Expand Down
Loading