Skip to content

Commit 12daf86

Browse files
committed
Implement GEO commands
1 parent ed00059 commit 12daf86

File tree

4 files changed

+65
-12
lines changed

4 files changed

+65
-12
lines changed

docs/redis-commands/Redis.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1198,30 +1198,30 @@ Returns members of a geospatial index as standard geohash strings
11981198

11991199
Returns longitude and latitude of members of a geospatial index
12001200

1201-
1202-
### Unsupported geo commands
1203-
> To implement support for a command, see [here](/guides/implement-command/)
1204-
1205-
#### [GEORADIUS](https://redis.io/commands/georadius/) <small>(not implemented)</small>
1201+
### [GEORADIUS](https://redis.io/commands/georadius/)
12061202

12071203
Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point
12081204

1209-
#### [GEORADIUSBYMEMBER](https://redis.io/commands/georadiusbymember/) <small>(not implemented)</small>
1205+
### [GEORADIUSBYMEMBER](https://redis.io/commands/georadiusbymember/)
12101206

12111207
Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member
12121208

1213-
#### [GEORADIUSBYMEMBER_RO](https://redis.io/commands/georadiusbymember_ro/) <small>(not implemented)</small>
1209+
### [GEORADIUSBYMEMBER_RO](https://redis.io/commands/georadiusbymember_ro/)
12141210

12151211
A read-only variant for GEORADIUSBYMEMBER
12161212

1217-
#### [GEORADIUS_RO](https://redis.io/commands/georadius_ro/) <small>(not implemented)</small>
1213+
### [GEORADIUS_RO](https://redis.io/commands/georadius_ro/)
12181214

12191215
A read-only variant for GEORADIUS
12201216

1221-
#### [GEOSEARCH](https://redis.io/commands/geosearch/) <small>(not implemented)</small>
1217+
### [GEOSEARCH](https://redis.io/commands/geosearch/)
12221218

12231219
Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle.
12241220

1221+
1222+
### Unsupported geo commands
1223+
> To implement support for a command, see [here](/guides/implement-command/)
1224+
12251225
#### [GEOSEARCHSTORE](https://redis.io/commands/geosearchstore/) <small>(not implemented)</small>
12261226

12271227
Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle, and store the result in another key.

fakeredis/_command_args_parsing.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
from typing import Tuple, List, Dict, Any
22

33
from . import _msgs as msgs
4-
from ._commands import Int
4+
from ._commands import Int, Float
55
from ._helpers import SimpleError, null_terminate
66

77

88
def _count_params(s: str):
99
res = 0
10-
while s[res] in '+*~':
10+
while s[res] in '.+*~':
1111
res += 1
1212
return res
1313

@@ -54,6 +54,7 @@ def extract_args(
5454
5555
An expected argument can have parameters:
5656
- A numerical (Int) parameter is identified with +.
57+
- A float (Float) parameter is identified with .
5758
- A non-numerical parameter is identified with a *.
5859
- A argument with potentially ~ or = between the
5960
argument name and the value is identified with a ~.
@@ -110,6 +111,8 @@ def _parse_params(
110111
curr_arg = actual_args[ind + i + 1]
111112
if argument_name[i] == '+':
112113
curr_arg = Int.decode(curr_arg)
114+
elif argument_name[i] == '.':
115+
curr_arg = Float.decode(curr_arg)
113116
temp_res.append(curr_arg)
114117

115118
if len(temp_res) == 1:

fakeredis/commands_mixins/geo_mixin.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ def geodist(self, key, m1, m2, *args):
142142

143143
def _search(
144144
self, key, long, lat, radius, conv,
145-
withcoord, withdist, withhash, count, count_any, desc, store, storedist):
145+
withcoord, withdist, _, count, count_any, desc, store, storedist):
146146
zset = key.value
147147
geo_results = _find_near(zset, lat, long, radius, conv, count, count_any, desc)
148148

@@ -188,3 +188,17 @@ def georadiusbymember_ro(self, key, member_name, radius, *args):
188188
member_score = key.value.get(member_name)
189189
lat, long, _, _ = geohash.decode(member_score)
190190
return self.georadius_ro(key, long, lat, radius, *args)
191+
192+
@command(name='GEOSEARCH', fixed=(Key(ZSet),), repeat=(bytes,))
193+
def geosearch(self, key, *args):
194+
(frommember, (long, lat), radius), left_args = extract_args(
195+
args, ('*frommember', '..fromlonlat', '.byradius'),
196+
error_on_unexpected=False, left_from_first_unexpected=False)
197+
if frommember is None and long is None:
198+
raise SimpleError(msgs.SYNTAX_ERROR_MSG)
199+
if frommember is not None and long is not None:
200+
raise SimpleError(msgs.SYNTAX_ERROR_MSG)
201+
if frommember:
202+
return self.georadiusbymember_ro(key, frommember, radius, *left_args)
203+
else:
204+
return self.georadius_ro(key, long, lat, radius, *left_args)

test/test_mixins/test_geo_commands.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,25 @@ def test_georadius(
120120
assert r.georadius("barcelona", long, lat, radius, **extra) == expected
121121

122122

123+
@pytest.mark.parametrize(
124+
"member,radius,extra,expected", [
125+
('place1', 1000, {}, [b"place1"]),
126+
('place2', 1000, {}, [b"place2"]),
127+
('place1', 1, {"unit": "km"}, [b"place1"]),
128+
('place1', 3000, {"count": 1}, [b"place1"]),
129+
])
130+
def test_georadiusbymember(
131+
r: redis.Redis, member: str, radius: float,
132+
extra: Dict[str, Any],
133+
expected):
134+
values = ((2.1909389952632, 41.433791470673, "place1") +
135+
(2.1873744593677, 41.406342043777, b"place2"))
136+
r.geoadd("barcelona", values)
137+
assert r.georadiusbymember("barcelona", member, radius, **extra) == expected
138+
assert r.georadiusbymember("barcelona", member, radius, **extra, store_dist='extract') == len(expected)
139+
assert r.zcard("extract") == len(expected)
140+
141+
123142
def test_georadius_with(r: redis.Redis):
124143
values = ((2.1909389952632, 41.433791470673, "place1") +
125144
(2.1873744593677, 41.406342043777, "place2",))
@@ -180,3 +199,20 @@ def test_georadius_errors(r: redis.Redis):
180199
r.geoadd('newgroup', bad_values)
181200
with pytest.raises(redis.ResponseError):
182201
testtools.raw_command(r, 'geoadd', 'newgroup', *bad_values)
202+
203+
204+
def test_geosearch(r: redis.Redis):
205+
values = (
206+
(2.1909389952632, 41.433791470673, "place1")
207+
+ (2.1873744593677, 41.406342043777, b"place2")
208+
+ (2.583333, 41.316667, "place3")
209+
)
210+
r.geoadd("barcelona", values)
211+
assert r.geosearch("barcelona", longitude=2.191, latitude=41.433, radius=1000) == [b"place1"]
212+
assert r.geosearch("barcelona", longitude=2.187, latitude=41.406, radius=1000) == [b"place2"]
213+
# assert r.geosearch("barcelona", longitude=2.191, latitude=41.433, height=1000, width=1000) == [b"place1"]
214+
assert set(r.geosearch("barcelona", member="place3", radius=100, unit="km")) == {b"place2", b"place1", b"place3", }
215+
# test count
216+
assert r.geosearch("barcelona", member="place3", radius=100, unit="km", count=2) == [b"place3", b"place2"]
217+
assert r.geosearch("barcelona", member="place3", radius=100, unit="km", count=1, any=1)[0] in [
218+
b"place1", b"place3", b"place2"]

0 commit comments

Comments
 (0)