diff --git a/docs/redis-commands/Redis.md b/docs/redis-commands/Redis.md
index 4ba00d34..6f58c4f8 100644
--- a/docs/redis-commands/Redis.md
+++ b/docs/redis-commands/Redis.md
@@ -1198,30 +1198,30 @@ Returns members of a geospatial index as standard geohash strings
Returns longitude and latitude of members of a geospatial index
-
-### Unsupported geo commands
-> To implement support for a command, see [here](/guides/implement-command/)
-
-#### [GEORADIUS](https://redis.io/commands/georadius/) (not implemented)
+### [GEORADIUS](https://redis.io/commands/georadius/)
Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a point
-#### [GEORADIUSBYMEMBER](https://redis.io/commands/georadiusbymember/) (not implemented)
+### [GEORADIUSBYMEMBER](https://redis.io/commands/georadiusbymember/)
Query a sorted set representing a geospatial index to fetch members matching a given maximum distance from a member
-#### [GEORADIUSBYMEMBER_RO](https://redis.io/commands/georadiusbymember_ro/) (not implemented)
+### [GEORADIUSBYMEMBER_RO](https://redis.io/commands/georadiusbymember_ro/)
A read-only variant for GEORADIUSBYMEMBER
-#### [GEORADIUS_RO](https://redis.io/commands/georadius_ro/) (not implemented)
+### [GEORADIUS_RO](https://redis.io/commands/georadius_ro/)
A read-only variant for GEORADIUS
-#### [GEOSEARCH](https://redis.io/commands/geosearch/) (not implemented)
+### [GEOSEARCH](https://redis.io/commands/geosearch/)
Query a sorted set representing a geospatial index to fetch members inside an area of a box or a circle.
+
+### Unsupported geo commands
+> To implement support for a command, see [here](/guides/implement-command/)
+
#### [GEOSEARCHSTORE](https://redis.io/commands/geosearchstore/) (not implemented)
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.
diff --git a/fakeredis/_command_args_parsing.py b/fakeredis/_command_args_parsing.py
index f0c4dc89..f9544901 100644
--- a/fakeredis/_command_args_parsing.py
+++ b/fakeredis/_command_args_parsing.py
@@ -1,13 +1,13 @@
from typing import Tuple, List, Dict, Any
from . import _msgs as msgs
-from ._commands import Int
+from ._commands import Int, Float
from ._helpers import SimpleError, null_terminate
def _count_params(s: str):
res = 0
- while s[res] in '+*~':
+ while s[res] in '.+*~':
res += 1
return res
@@ -54,6 +54,7 @@ def extract_args(
An expected argument can have parameters:
- A numerical (Int) parameter is identified with +.
+ - A float (Float) parameter is identified with .
- A non-numerical parameter is identified with a *.
- A argument with potentially ~ or = between the
argument name and the value is identified with a ~.
@@ -110,6 +111,8 @@ def _parse_params(
curr_arg = actual_args[ind + i + 1]
if argument_name[i] == '+':
curr_arg = Int.decode(curr_arg)
+ elif argument_name[i] == '.':
+ curr_arg = Float.decode(curr_arg)
temp_res.append(curr_arg)
if len(temp_res) == 1:
diff --git a/fakeredis/commands_mixins/geo_mixin.py b/fakeredis/commands_mixins/geo_mixin.py
index 80551a9d..5111e356 100644
--- a/fakeredis/commands_mixins/geo_mixin.py
+++ b/fakeredis/commands_mixins/geo_mixin.py
@@ -142,7 +142,7 @@ def geodist(self, key, m1, m2, *args):
def _search(
self, key, long, lat, radius, conv,
- withcoord, withdist, withhash, count, count_any, desc, store, storedist):
+ withcoord, withdist, _, count, count_any, desc, store, storedist):
zset = key.value
geo_results = _find_near(zset, lat, long, radius, conv, count, count_any, desc)
@@ -188,3 +188,17 @@ def georadiusbymember_ro(self, key, member_name, radius, *args):
member_score = key.value.get(member_name)
lat, long, _, _ = geohash.decode(member_score)
return self.georadius_ro(key, long, lat, radius, *args)
+
+ @command(name='GEOSEARCH', fixed=(Key(ZSet),), repeat=(bytes,))
+ def geosearch(self, key, *args):
+ (frommember, (long, lat), radius), left_args = extract_args(
+ args, ('*frommember', '..fromlonlat', '.byradius'),
+ error_on_unexpected=False, left_from_first_unexpected=False)
+ if frommember is None and long is None:
+ raise SimpleError(msgs.SYNTAX_ERROR_MSG)
+ if frommember is not None and long is not None:
+ raise SimpleError(msgs.SYNTAX_ERROR_MSG)
+ if frommember:
+ return self.georadiusbymember_ro(key, frommember, radius, *left_args)
+ else:
+ return self.georadius_ro(key, long, lat, radius, *left_args)
diff --git a/test/test_mixins/test_geo_commands.py b/test/test_mixins/test_geo_commands.py
index d23068ed..bfced432 100644
--- a/test/test_mixins/test_geo_commands.py
+++ b/test/test_mixins/test_geo_commands.py
@@ -120,6 +120,25 @@ def test_georadius(
assert r.georadius("barcelona", long, lat, radius, **extra) == expected
+@pytest.mark.parametrize(
+ "member,radius,extra,expected", [
+ ('place1', 1000, {}, [b"place1"]),
+ ('place2', 1000, {}, [b"place2"]),
+ ('place1', 1, {"unit": "km"}, [b"place1"]),
+ ('place1', 3000, {"count": 1}, [b"place1"]),
+ ])
+def test_georadiusbymember(
+ r: redis.Redis, member: str, radius: float,
+ extra: Dict[str, Any],
+ expected):
+ values = ((2.1909389952632, 41.433791470673, "place1") +
+ (2.1873744593677, 41.406342043777, b"place2"))
+ r.geoadd("barcelona", values)
+ assert r.georadiusbymember("barcelona", member, radius, **extra) == expected
+ assert r.georadiusbymember("barcelona", member, radius, **extra, store_dist='extract') == len(expected)
+ assert r.zcard("extract") == len(expected)
+
+
def test_georadius_with(r: redis.Redis):
values = ((2.1909389952632, 41.433791470673, "place1") +
(2.1873744593677, 41.406342043777, "place2",))
@@ -180,3 +199,20 @@ def test_georadius_errors(r: redis.Redis):
r.geoadd('newgroup', bad_values)
with pytest.raises(redis.ResponseError):
testtools.raw_command(r, 'geoadd', 'newgroup', *bad_values)
+
+
+def test_geosearch(r: redis.Redis):
+ values = (
+ (2.1909389952632, 41.433791470673, "place1")
+ + (2.1873744593677, 41.406342043777, b"place2")
+ + (2.583333, 41.316667, "place3")
+ )
+ r.geoadd("barcelona", values)
+ assert r.geosearch("barcelona", longitude=2.191, latitude=41.433, radius=1000) == [b"place1"]
+ assert r.geosearch("barcelona", longitude=2.187, latitude=41.406, radius=1000) == [b"place2"]
+ # assert r.geosearch("barcelona", longitude=2.191, latitude=41.433, height=1000, width=1000) == [b"place1"]
+ assert set(r.geosearch("barcelona", member="place3", radius=100, unit="km")) == {b"place2", b"place1", b"place3", }
+ # test count
+ assert r.geosearch("barcelona", member="place3", radius=100, unit="km", count=2) == [b"place3", b"place2"]
+ assert r.geosearch("barcelona", member="place3", radius=100, unit="km", count=1, any=1)[0] in [
+ b"place1", b"place3", b"place2"]