diff options
author | Avital Fine <79420960+AvitalFineRedis@users.noreply.github.com> | 2021-09-01 13:13:42 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-01 13:13:42 +0300 |
commit | 0f8d0dcbb3a7f759843f3f89e413f9333d027c98 (patch) | |
tree | 52436a142a53673173f59d530394d0d09137ee19 | |
parent | e53227cf68c065b4d31f39cdde7c85c5e91dd1bf (diff) | |
download | redis-py-0f8d0dcbb3a7f759843f3f89e413f9333d027c98.tar.gz |
GEOSEARCH and GEOSEARCHSTORE (#1526)
* GEOSEARCH and GEOSEARCHSTORE
* negative test
* change georadius_generic to geosearch_generic
* add documentations to the functions
* add docstring to the parser
* farest
-rwxr-xr-x | redis/client.py | 16 | ||||
-rw-r--r-- | redis/commands.py | 130 | ||||
-rw-r--r-- | tests/test_commands.py | 310 |
3 files changed, 370 insertions, 86 deletions
diff --git a/redis/client.py b/redis/client.py index a4d7f6b..019939a 100755 --- a/redis/client.py +++ b/redis/client.py @@ -472,10 +472,15 @@ def parse_cluster_nodes(response, **options): return dict(_parse_node_line(line) for line in raw_lines) -def parse_georadius_generic(response, **options): +def parse_geosearch_generic(response, **options): + """ + Parse the response of 'GEOSEARCH', GEORADIUS' and 'GEORADIUSBYMEMBER' + commands according to 'withdist', 'withhash' and 'withcoord' labels. + """ if options['store'] or options['store_dist']: - # `store` and `store_diff` cant be combined + # `store` and `store_dist` cant be combined # with other command arguments. + # relevant to 'GEORADIUS' and 'GEORADIUSBYMEMBER' return response if type(response) != list: @@ -483,7 +488,7 @@ def parse_georadius_generic(response, **options): else: response_list = response - if not options['withdist'] and not options['withcoord']\ + if not options['withdist'] and not options['withcoord'] \ and not options['withhash']: # just a bunch of places return response_list @@ -695,8 +700,9 @@ class Redis(Commands, object): 'GEOPOS': lambda r: list(map(lambda ll: (float(ll[0]), float(ll[1])) if ll is not None else None, r)), - 'GEORADIUS': parse_georadius_generic, - 'GEORADIUSBYMEMBER': parse_georadius_generic, + 'GEOSEARCH': parse_geosearch_generic, + 'GEORADIUS': parse_geosearch_generic, + 'GEORADIUSBYMEMBER': parse_geosearch_generic, 'HGETALL': lambda r: r and pairs_to_dict(r) or {}, 'HSCAN': parse_hscan, 'INFO': parse_info, diff --git a/redis/commands.py b/redis/commands.py index 6e816de..4148f98 100644 --- a/redis/commands.py +++ b/redis/commands.py @@ -2924,6 +2924,136 @@ class Commands: return self.execute_command(command, *pieces, **kwargs) + def geosearch(self, name, member=None, longitude=None, latitude=None, + unit='m', radius=None, width=None, height=None, sort=None, + count=None, any=False, withcoord=False, + withdist=False, withhash=False): + """ + Return the members of specified key identified by the + ``name`` argument, which are within the borders of the + area specified by a given shape. This command extends the + GEORADIUS command, so in addition to searching within circular + areas, it supports searching within rectangular areas. + This command should be used in place of the deprecated + GEORADIUS and GEORADIUSBYMEMBER commands. + ``member`` Use the position of the given existing + member in the sorted set. Can't be given with ``longitude`` + and ``latitude``. + ``longitude`` and ``latitude`` Use the position given by + this coordinates. Can't be given with ``member`` + ``radius`` Similar to GEORADIUS, search inside circular + area according the given radius. Can't be given with + ``height`` and ``width``. + ``height`` and ``width`` Search inside an axis-aligned + rectangle, determined by the given height and width. + Can't be given with ``radius`` + ``unit`` must be one of the following : m, km, mi, ft. + `m` for meters (the default value), `km` for kilometers, + `mi` for miles and `ft` for feet. + ``sort`` indicates to return the places in a sorted way, + ASC for nearest to farest and DESC for farest to nearest. + ``count`` limit the results to the first count matching items. + ``any`` is set to True, the command will return as soon as + enough matches are found. Can't be provided without ``count`` + ``withdist`` indicates to return the distances of each place. + ``withcoord`` indicates to return the latitude and longitude of + each place. + ``withhash`` indicates to return the geohash string of each place. + """ + + return self._geosearchgeneric('GEOSEARCH', + name, member=member, longitude=longitude, + latitude=latitude, unit=unit, + radius=radius, width=width, + height=height, sort=sort, count=count, + any=any, withcoord=withcoord, + withdist=withdist, withhash=withhash, + store=None, store_dist=None) + + def geosearchstore(self, dest, name, member=None, longitude=None, + latitude=None, unit='m', radius=None, width=None, + height=None, sort=None, count=None, any=False, + storedist=False): + """ + This command is like GEOSEARCH, but stores the result in + ``dest``. By default, it stores the results in the destination + sorted set with their geospatial information. + if ``store_dist`` set to True, the command will stores the + items in a sorted set populated with their distance from the + center of the circle or box, as a floating-point number. + """ + return self._geosearchgeneric('GEOSEARCHSTORE', + dest, name, member=member, + longitude=longitude, latitude=latitude, + unit=unit, radius=radius, width=width, + height=height, sort=sort, count=count, + any=any, withcoord=None, + withdist=None, withhash=None, + store=None, store_dist=storedist) + + def _geosearchgeneric(self, command, *args, **kwargs): + pieces = list(args) + + # FROMMEMBER or FROMLONLAT + if kwargs['member'] is None: + if kwargs['longitude'] is None or kwargs['latitude'] is None: + raise DataError("GEOSEARCH must have member or" + " longitude and latitude") + if kwargs['member']: + if kwargs['longitude'] or kwargs['latitude']: + raise DataError("GEOSEARCH member and longitude or latitude" + " cant be set together") + pieces.extend([b'FROMMEMBER', kwargs['member']]) + if kwargs['longitude'] and kwargs['latitude']: + pieces.extend([b'FROMLONLAT', + kwargs['longitude'], kwargs['latitude']]) + + # BYRADIUS or BYBOX + if kwargs['radius'] is None: + if kwargs['width'] is None or kwargs['height'] is None: + raise DataError("GEOSEARCH must have radius or" + " width and height") + if kwargs['unit'] is None: + raise DataError("GEOSEARCH must have unit") + if kwargs['unit'].lower() not in ('m', 'km', 'mi', 'ft'): + raise DataError("GEOSEARCH invalid unit") + if kwargs['radius']: + if kwargs['width'] or kwargs['height']: + raise DataError("GEOSEARCH radius and width or height" + " cant be set together") + pieces.extend([b'BYRADIUS', kwargs['radius'], kwargs['unit']]) + if kwargs['width'] and kwargs['height']: + pieces.extend([b'BYBOX', + kwargs['width'], kwargs['height'], kwargs['unit']]) + + # sort + if kwargs['sort']: + if kwargs['sort'].upper() == 'ASC': + pieces.append(b'ASC') + elif kwargs['sort'].upper() == 'DESC': + pieces.append(b'DESC') + else: + raise DataError("GEOSEARCH invalid sort") + + # count any + if kwargs['count']: + pieces.extend([b'COUNT', kwargs['count']]) + if kwargs['any']: + pieces.append(b'ANY') + elif kwargs['any']: + raise DataError("GEOSEARCH any can't be provided without count") + + # other properties + for arg_name, byte_repr in ( + ('withdist', b'WITHDIST'), + ('withcoord', b'WITHCOORD'), + ('withhash', b'WITHHASH'), + ('store_dist', b'STOREDIST')): + if kwargs[arg_name]: + pieces.append(byte_repr) + + return self.execute_command(command, *pieces, **kwargs) + # MODULE COMMANDS def module_load(self, path): """ diff --git a/tests/test_commands.py b/tests/test_commands.py index 1698106..30ad5d5 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -1237,7 +1237,7 @@ class TestRedisCommands: assert r.lrange('a', 0, -1) == [b'1', b'2', b'2.5', b'3'] assert r.linsert('a', 'before', '2', '1.5') == 5 assert r.lrange('a', 0, -1) == \ - [b'1', b'1.5', b'2', b'2.5', b'3'] + [b'1', b'1.5', b'2', b'2.5', b'3'] def test_llen(self, r): r.rpush('a', '1', '2', '3') @@ -1567,7 +1567,7 @@ class TestRedisCommands: mapping = {'a1': 1.0, 'a2': 2.0, 'a3': 3.0} r.zadd('a', mapping) assert r.zrange('a', 0, -1, withscores=True) == \ - [(b'a1', 1.0), (b'a2', 2.0), (b'a3', 3.0)] + [(b'a1', 1.0), (b'a2', 2.0), (b'a3', 3.0)] # error cases with pytest.raises(exceptions.DataError): @@ -1585,19 +1585,19 @@ class TestRedisCommands: assert r.zadd('a', {'a1': 1}) == 1 assert r.zadd('a', {'a1': 99, 'a2': 2}, nx=True) == 1 assert r.zrange('a', 0, -1, withscores=True) == \ - [(b'a1', 1.0), (b'a2', 2.0)] + [(b'a1', 1.0), (b'a2', 2.0)] def test_zadd_xx(self, r): assert r.zadd('a', {'a1': 1}) == 1 assert r.zadd('a', {'a1': 99, 'a2': 2}, xx=True) == 0 assert r.zrange('a', 0, -1, withscores=True) == \ - [(b'a1', 99.0)] + [(b'a1', 99.0)] def test_zadd_ch(self, r): assert r.zadd('a', {'a1': 1}) == 1 assert r.zadd('a', {'a1': 99, 'a2': 2}, ch=True) == 2 assert r.zrange('a', 0, -1, withscores=True) == \ - [(b'a2', 2.0), (b'a1', 99.0)] + [(b'a2', 2.0), (b'a1', 99.0)] def test_zadd_incr(self, r): assert r.zadd('a', {'a1': 1}) == 1 @@ -1694,7 +1694,7 @@ class TestRedisCommands: r.zadd('c', {'a1': 6, 'a3': 5, 'a4': 4}) assert r.zinterstore('d', ['a', 'b', 'c']) == 2 assert r.zrange('d', 0, -1, withscores=True) == \ - [(b'a3', 8), (b'a1', 9)] + [(b'a3', 8), (b'a1', 9)] def test_zinterstore_max(self, r): r.zadd('a', {'a1': 1, 'a2': 1, 'a3': 1}) @@ -1702,7 +1702,7 @@ class TestRedisCommands: r.zadd('c', {'a1': 6, 'a3': 5, 'a4': 4}) assert r.zinterstore('d', ['a', 'b', 'c'], aggregate='MAX') == 2 assert r.zrange('d', 0, -1, withscores=True) == \ - [(b'a3', 5), (b'a1', 6)] + [(b'a3', 5), (b'a1', 6)] def test_zinterstore_min(self, r): r.zadd('a', {'a1': 1, 'a2': 2, 'a3': 3}) @@ -1710,7 +1710,7 @@ class TestRedisCommands: r.zadd('c', {'a1': 6, 'a3': 5, 'a4': 4}) assert r.zinterstore('d', ['a', 'b', 'c'], aggregate='MIN') == 2 assert r.zrange('d', 0, -1, withscores=True) == \ - [(b'a1', 1), (b'a3', 3)] + [(b'a1', 1), (b'a3', 3)] def test_zinterstore_with_weight(self, r): r.zadd('a', {'a1': 1, 'a2': 1, 'a3': 1}) @@ -1718,7 +1718,7 @@ class TestRedisCommands: r.zadd('c', {'a1': 6, 'a3': 5, 'a4': 4}) assert r.zinterstore('d', {'a': 1, 'b': 2, 'c': 3}) == 2 assert r.zrange('d', 0, -1, withscores=True) == \ - [(b'a3', 20), (b'a1', 23)] + [(b'a3', 20), (b'a1', 23)] @skip_if_server_version_lt('4.9.0') def test_zpopmax(self, r): @@ -1727,7 +1727,7 @@ class TestRedisCommands: # with count assert r.zpopmax('a', count=2) == \ - [(b'a2', 2), (b'a1', 1)] + [(b'a2', 2), (b'a1', 1)] @skip_if_server_version_lt('4.9.0') def test_zpopmin(self, r): @@ -1736,7 +1736,7 @@ class TestRedisCommands: # with count assert r.zpopmin('a', count=2) == \ - [(b'a2', 2), (b'a3', 3)] + [(b'a2', 2), (b'a3', 3)] @skip_if_server_version_lt('6.2.0') def test_zrandemember(self, r): @@ -1781,13 +1781,13 @@ class TestRedisCommands: # withscores assert r.zrange('a', 0, 1, withscores=True) == \ - [(b'a1', 1.0), (b'a2', 2.0)] + [(b'a1', 1.0), (b'a2', 2.0)] assert r.zrange('a', 1, 2, withscores=True) == \ - [(b'a2', 2.0), (b'a3', 3.0)] + [(b'a2', 2.0), (b'a3', 3.0)] # custom score function assert r.zrange('a', 0, 1, withscores=True, score_cast_func=int) == \ - [(b'a1', 1), (b'a2', 2)] + [(b'a1', 1), (b'a2', 2)] @skip_if_server_version_lt('6.2.0') def test_zrangestore(self, r): @@ -1805,7 +1805,7 @@ class TestRedisCommands: assert r.zrangebylex('a', '-', '[c') == [b'a', b'b', b'c'] assert r.zrangebylex('a', '-', '(c') == [b'a', b'b'] assert r.zrangebylex('a', '[aaa', '(g') == \ - [b'b', b'c', b'd', b'e', b'f'] + [b'b', b'c', b'd', b'e', b'f'] assert r.zrangebylex('a', '[f', '+') == [b'f', b'g'] assert r.zrangebylex('a', '-', '+', start=3, num=2) == [b'd', b'e'] @@ -1815,10 +1815,10 @@ class TestRedisCommands: assert r.zrevrangebylex('a', '[c', '-') == [b'c', b'b', b'a'] assert r.zrevrangebylex('a', '(c', '-') == [b'b', b'a'] assert r.zrevrangebylex('a', '(g', '[aaa') == \ - [b'f', b'e', b'd', b'c', b'b'] + [b'f', b'e', b'd', b'c', b'b'] assert r.zrevrangebylex('a', '+', '[f') == [b'g', b'f'] assert r.zrevrangebylex('a', '+', '-', start=3, num=2) == \ - [b'd', b'c'] + [b'd', b'c'] def test_zrangebyscore(self, r): r.zadd('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5}) @@ -1826,16 +1826,16 @@ class TestRedisCommands: # slicing with start/num assert r.zrangebyscore('a', 2, 4, start=1, num=2) == \ - [b'a3', b'a4'] + [b'a3', b'a4'] # withscores assert r.zrangebyscore('a', 2, 4, withscores=True) == \ - [(b'a2', 2.0), (b'a3', 3.0), (b'a4', 4.0)] + [(b'a2', 2.0), (b'a3', 3.0), (b'a4', 4.0)] # custom score function assert r.zrangebyscore('a', 2, 4, withscores=True, score_cast_func=int) == \ - [(b'a2', 2), (b'a3', 3), (b'a4', 4)] + [(b'a2', 2), (b'a3', 3), (b'a4', 4)] def test_zrank(self, r): r.zadd('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5}) @@ -1884,14 +1884,14 @@ class TestRedisCommands: # withscores assert r.zrevrange('a', 0, 1, withscores=True) == \ - [(b'a3', 3.0), (b'a2', 2.0)] + [(b'a3', 3.0), (b'a2', 2.0)] assert r.zrevrange('a', 1, 2, withscores=True) == \ - [(b'a2', 2.0), (b'a1', 1.0)] + [(b'a2', 2.0), (b'a1', 1.0)] # custom score function assert r.zrevrange('a', 0, 1, withscores=True, score_cast_func=int) == \ - [(b'a3', 3.0), (b'a2', 2.0)] + [(b'a3', 3.0), (b'a2', 2.0)] def test_zrevrangebyscore(self, r): r.zadd('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5}) @@ -1899,16 +1899,16 @@ class TestRedisCommands: # slicing with start/num assert r.zrevrangebyscore('a', 4, 2, start=1, num=2) == \ - [b'a3', b'a2'] + [b'a3', b'a2'] # withscores assert r.zrevrangebyscore('a', 4, 2, withscores=True) == \ - [(b'a4', 4.0), (b'a3', 3.0), (b'a2', 2.0)] + [(b'a4', 4.0), (b'a3', 3.0), (b'a2', 2.0)] # custom score function assert r.zrevrangebyscore('a', 4, 2, withscores=True, score_cast_func=int) == \ - [(b'a4', 4), (b'a3', 3), (b'a2', 2)] + [(b'a4', 4), (b'a3', 3), (b'a2', 2)] def test_zrevrank(self, r): r.zadd('a', {'a1': 1, 'a2': 2, 'a3': 3, 'a4': 4, 'a5': 5}) @@ -1948,7 +1948,7 @@ class TestRedisCommands: r.zadd('c', {'a1': 6, 'a3': 5, 'a4': 4}) assert r.zunionstore('d', ['a', 'b', 'c']) == 4 assert r.zrange('d', 0, -1, withscores=True) == \ - [(b'a2', 3), (b'a4', 4), (b'a3', 8), (b'a1', 9)] + [(b'a2', 3), (b'a4', 4), (b'a3', 8), (b'a1', 9)] def test_zunionstore_max(self, r): r.zadd('a', {'a1': 1, 'a2': 1, 'a3': 1}) @@ -1956,7 +1956,7 @@ class TestRedisCommands: r.zadd('c', {'a1': 6, 'a3': 5, 'a4': 4}) assert r.zunionstore('d', ['a', 'b', 'c'], aggregate='MAX') == 4 assert r.zrange('d', 0, -1, withscores=True) == \ - [(b'a2', 2), (b'a4', 4), (b'a3', 5), (b'a1', 6)] + [(b'a2', 2), (b'a4', 4), (b'a3', 5), (b'a1', 6)] def test_zunionstore_min(self, r): r.zadd('a', {'a1': 1, 'a2': 2, 'a3': 3}) @@ -1964,7 +1964,7 @@ class TestRedisCommands: r.zadd('c', {'a1': 6, 'a3': 5, 'a4': 4}) assert r.zunionstore('d', ['a', 'b', 'c'], aggregate='MIN') == 4 assert r.zrange('d', 0, -1, withscores=True) == \ - [(b'a1', 1), (b'a2', 2), (b'a3', 3), (b'a4', 4)] + [(b'a1', 1), (b'a2', 2), (b'a3', 3), (b'a4', 4)] def test_zunionstore_with_weight(self, r): r.zadd('a', {'a1': 1, 'a2': 1, 'a3': 1}) @@ -1972,7 +1972,7 @@ class TestRedisCommands: r.zadd('c', {'a1': 6, 'a3': 5, 'a4': 4}) assert r.zunionstore('d', {'a': 1, 'b': 2, 'c': 3}) == 4 assert r.zrange('d', 0, -1, withscores=True) == \ - [(b'a2', 5), (b'a4', 12), (b'a3', 20), (b'a1', 23)] + [(b'a2', 5), (b'a4', 12), (b'a3', 20), (b'a1', 23)] @skip_if_server_version_lt('6.1.240') def test_zmscore(self, r): @@ -2153,7 +2153,7 @@ class TestRedisCommands: r['user:3'] = 'u3' r.rpush('a', '2', '3', '1') assert r.sort('a', get=('user:*', '#')) == \ - [b'u1', b'1', b'u2', b'2', b'u3', b'3'] + [b'u1', b'1', b'u2', b'2', b'u3', b'3'] def test_sort_get_groups_two(self, r): r['user:1'] = 'u1' @@ -2161,7 +2161,7 @@ class TestRedisCommands: r['user:3'] = 'u3' r.rpush('a', '2', '3', '1') assert r.sort('a', get=('user:*', '#'), groups=True) == \ - [(b'u1', b'1'), (b'u2', b'2'), (b'u3', b'3')] + [(b'u1', b'1'), (b'u2', b'2'), (b'u3', b'3')] def test_sort_groups_string_get(self, r): r['user:1'] = 'u1' @@ -2196,11 +2196,11 @@ class TestRedisCommands: r['door:3'] = 'd3' r.rpush('a', '2', '3', '1') assert r.sort('a', get=('user:*', 'door:*', '#'), groups=True) == \ - [ - (b'u1', b'd1', b'1'), - (b'u2', b'd2', b'2'), - (b'u3', b'd3', b'3') - ] + [ + (b'u1', b'd1', b'1'), + (b'u2', b'd2', b'2'), + (b'u3', b'd3', b'3') + ] def test_sort_desc(self, r): r.rpush('a', '2', '3', '1') @@ -2209,7 +2209,7 @@ class TestRedisCommands: def test_sort_alpha(self, r): r.rpush('a', 'e', 'c', 'b', 'd', 'a') assert r.sort('a', alpha=True) == \ - [b'a', b'b', b'c', b'd', b'e'] + [b'a', b'b', b'c', b'd', b'e'] def test_sort_store(self, r): r.rpush('a', '2', '3', '1') @@ -2241,7 +2241,7 @@ class TestRedisCommands: store='sorted') assert num == 4 assert r.lrange('sorted', 0, 10) == \ - [b'vodka', b'milk', b'gin', b'apple juice'] + [b'vodka', b'milk', b'gin', b'apple juice'] def test_sort_issue_924(self, r): # Tests for issue https://github.com/andymccurdy/redis-py/issues/924 @@ -2314,7 +2314,7 @@ class TestRedisCommands: # GEO COMMANDS @skip_if_server_version_lt('3.2.0') def test_geoadd(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') assert r.geoadd('barcelona', *values) == 2 @@ -2327,7 +2327,7 @@ class TestRedisCommands: @skip_if_server_version_lt('3.2.0') def test_geodist(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') assert r.geoadd('barcelona', *values) == 2 @@ -2335,7 +2335,7 @@ class TestRedisCommands: @skip_if_server_version_lt('3.2.0') def test_geodist_units(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') r.geoadd('barcelona', *values) @@ -2354,24 +2354,24 @@ class TestRedisCommands: @skip_if_server_version_lt('3.2.0') def test_geohash(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') r.geoadd('barcelona', *values) - assert r.geohash('barcelona', 'place1', 'place2', 'place3') ==\ - ['sp3e9yg3kd0', 'sp3e9cbc3t0', None] + assert r.geohash('barcelona', 'place1', 'place2', 'place3') == \ + ['sp3e9yg3kd0', 'sp3e9cbc3t0', None] @skip_unless_arch_bits(64) @skip_if_server_version_lt('3.2.0') def test_geopos(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') r.geoadd('barcelona', *values) # redis uses 52 bits precision, hereby small errors may be introduced. - assert r.geopos('barcelona', 'place1', 'place2') ==\ - [(2.19093829393386841, 41.43379028184083523), - (2.18737632036209106, 41.40634178640635099)] + assert r.geopos('barcelona', 'place1', 'place2') == \ + [(2.19093829393386841, 41.43379028184083523), + (2.18737632036209106, 41.40634178640635099)] @skip_if_server_version_lt('4.0.0') def test_geopos_no_value(self, r): @@ -2382,9 +2382,157 @@ class TestRedisCommands: def test_old_geopos_no_value(self, r): assert r.geopos('barcelona', 'place1', 'place2') == [] + @skip_if_server_version_lt('6.2.0') + def test_geosearch(self, r): + values = (2.1909389952632, 41.433791470673, 'place1') + \ + (2.1873744593677, 41.406342043777, b'\x80place2') + \ + (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'\x80place2'] + assert r.geosearch('barcelona', longitude=2.191, latitude=41.433, + height=1000, width=1000) == [b'place1'] + assert r.geosearch('barcelona', member='place3', radius=100, + unit='km') == [b'\x80place2', b'place1', b'place3'] + # test count + assert r.geosearch('barcelona', member='place3', radius=100, + unit='km', count=2) == [b'place3', b'\x80place2'] + assert r.geosearch('barcelona', member='place3', radius=100, + unit='km', count=1, any=1)[0] \ + in [b'place1', b'place3', b'\x80place2'] + + @skip_unless_arch_bits(64) + @skip_if_server_version_lt('6.2.0') + def test_geosearch_member(self, r): + values = (2.1909389952632, 41.433791470673, 'place1') + \ + (2.1873744593677, 41.406342043777, b'\x80place2') + + r.geoadd('barcelona', *values) + assert r.geosearch('barcelona', member='place1', radius=4000) == \ + [b'\x80place2', b'place1'] + assert r.geosearch('barcelona', member='place1', radius=10) == \ + [b'place1'] + + assert r.geosearch('barcelona', member='place1', radius=4000, + withdist=True, + withcoord=True, + withhash=True) == \ + [[b'\x80place2', 3067.4157, 3471609625421029, + (2.187376320362091, 41.40634178640635)], + [b'place1', 0.0, 3471609698139488, + (2.1909382939338684, 41.433790281840835)]] + + @skip_if_server_version_lt('6.2.0') + def test_geosearch_sort(self, r): + values = (2.1909389952632, 41.433791470673, 'place1') + \ + (2.1873744593677, 41.406342043777, 'place2') + r.geoadd('barcelona', *values) + assert r.geosearch('barcelona', longitude=2.191, + latitude=41.433, radius=3000, sort='ASC') == \ + [b'place1', b'place2'] + assert r.geosearch('barcelona', longitude=2.191, + latitude=41.433, radius=3000, sort='DESC') == \ + [b'place2', b'place1'] + + @skip_unless_arch_bits(64) + @skip_if_server_version_lt('6.2.0') + def test_geosearch_with(self, r): + values = (2.1909389952632, 41.433791470673, 'place1') + \ + (2.1873744593677, 41.406342043777, 'place2') + r.geoadd('barcelona', *values) + + # test a bunch of combinations to test the parse response + # function. + assert r.geosearch('barcelona', longitude=2.191, latitude=41.433, + radius=1, unit='km', withdist=True, + withcoord=True, withhash=True) == \ + [[b'place1', 0.0881, 3471609698139488, + (2.19093829393386841, 41.43379028184083523)]] + assert r.geosearch('barcelona', longitude=2.191, latitude=41.433, + radius=1, unit='km', + withdist=True, withcoord=True) == \ + [[b'place1', 0.0881, + (2.19093829393386841, 41.43379028184083523)]] + assert r.geosearch('barcelona', longitude=2.191, latitude=41.433, + radius=1, unit='km', + withhash=True, withcoord=True) == \ + [[b'place1', 3471609698139488, + (2.19093829393386841, 41.43379028184083523)]] + # test no values. + assert r.geosearch('barcelona', longitude=2, latitude=1, + radius=1, unit='km', withdist=True, + withcoord=True, withhash=True) == [] + + @skip_if_server_version_lt('6.2.0') + def test_geosearch_negative(self, r): + # not specifying member nor longitude and latitude + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona') + # specifying member and longitude and latitude + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona', + member="Paris", longitude=2, latitude=1) + # specifying one of longitude and latitude + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona', longitude=2) + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona', latitude=2) + + # not specifying radius nor width and height + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona', member="Paris") + # specifying radius and width and height + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona', member="Paris", + radius=3, width=2, height=1) + # specifying one of width and height + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona', member="Paris", width=2) + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona', member="Paris", height=2) + + # invalid sort + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona', + member="Paris", width=2, height=2, sort="wrong") + + # invalid unit + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona', + member="Paris", width=2, height=2, unit="miles") + + # use any without count + with pytest.raises(exceptions.DataError): + assert r.geosearch('barcelona', member='place3', radius=100, any=1) + + @skip_if_server_version_lt('6.2.0') + def test_geosearchstore(self, r): + values = (2.1909389952632, 41.433791470673, 'place1') + \ + (2.1873744593677, 41.406342043777, 'place2') + + r.geoadd('barcelona', *values) + r.geosearchstore('places_barcelona', 'barcelona', + longitude=2.191, latitude=41.433, radius=1000) + assert r.zrange('places_barcelona', 0, -1) == [b'place1'] + + @skip_unless_arch_bits(64) + @skip_if_server_version_lt('6.2.0') + def test_geosearchstore_dist(self, r): + values = (2.1909389952632, 41.433791470673, 'place1') + \ + (2.1873744593677, 41.406342043777, 'place2') + + r.geoadd('barcelona', *values) + r.geosearchstore('places_barcelona', 'barcelona', + longitude=2.191, latitude=41.433, + radius=1000, storedist=True) + # instead of save the geo score, the distance is saved. + assert r.zscore('places_barcelona', 'place1') == 88.05060698409301 + @skip_if_server_version_lt('3.2.0') def test_georadius(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, b'\x80place2') r.geoadd('barcelona', *values) @@ -2393,7 +2541,7 @@ class TestRedisCommands: @skip_if_server_version_lt('3.2.0') def test_georadius_no_values(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') r.geoadd('barcelona', *values) @@ -2401,17 +2549,17 @@ class TestRedisCommands: @skip_if_server_version_lt('3.2.0') def test_georadius_units(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') r.geoadd('barcelona', *values) - assert r.georadius('barcelona', 2.191, 41.433, 1, unit='km') ==\ - [b'place1'] + assert r.georadius('barcelona', 2.191, 41.433, 1, unit='km') == \ + [b'place1'] @skip_unless_arch_bits(64) @skip_if_server_version_lt('3.2.0') def test_georadius_with(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') r.geoadd('barcelona', *values) @@ -2419,19 +2567,19 @@ class TestRedisCommands: # test a bunch of combinations to test the parse response # function. assert r.georadius('barcelona', 2.191, 41.433, 1, unit='km', - withdist=True, withcoord=True, withhash=True) ==\ - [[b'place1', 0.0881, 3471609698139488, - (2.19093829393386841, 41.43379028184083523)]] + withdist=True, withcoord=True, withhash=True) == \ + [[b'place1', 0.0881, 3471609698139488, + (2.19093829393386841, 41.43379028184083523)]] assert r.georadius('barcelona', 2.191, 41.433, 1, unit='km', - withdist=True, withcoord=True) ==\ - [[b'place1', 0.0881, - (2.19093829393386841, 41.43379028184083523)]] + withdist=True, withcoord=True) == \ + [[b'place1', 0.0881, + (2.19093829393386841, 41.43379028184083523)]] assert r.georadius('barcelona', 2.191, 41.433, 1, unit='km', - withhash=True, withcoord=True) ==\ - [[b'place1', 3471609698139488, - (2.19093829393386841, 41.43379028184083523)]] + withhash=True, withcoord=True) == \ + [[b'place1', 3471609698139488, + (2.19093829393386841, 41.43379028184083523)]] # test no values. assert r.georadius('barcelona', 2, 1, 1, unit='km', @@ -2439,27 +2587,27 @@ class TestRedisCommands: @skip_if_server_version_lt('3.2.0') def test_georadius_count(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') r.geoadd('barcelona', *values) - assert r.georadius('barcelona', 2.191, 41.433, 3000, count=1) ==\ - [b'place1'] + assert r.georadius('barcelona', 2.191, 41.433, 3000, count=1) == \ + [b'place1'] @skip_if_server_version_lt('3.2.0') def test_georadius_sort(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') r.geoadd('barcelona', *values) - assert r.georadius('barcelona', 2.191, 41.433, 3000, sort='ASC') ==\ - [b'place1', b'place2'] - assert r.georadius('barcelona', 2.191, 41.433, 3000, sort='DESC') ==\ - [b'place2', b'place1'] + assert r.georadius('barcelona', 2.191, 41.433, 3000, sort='ASC') == \ + [b'place1', b'place2'] + assert r.georadius('barcelona', 2.191, 41.433, 3000, sort='DESC') == \ + [b'place2', b'place1'] @skip_if_server_version_lt('3.2.0') def test_georadius_store(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') r.geoadd('barcelona', *values) @@ -2469,7 +2617,7 @@ class TestRedisCommands: @skip_unless_arch_bits(64) @skip_if_server_version_lt('3.2.0') def test_georadius_store_dist(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, 'place2') r.geoadd('barcelona', *values) @@ -2481,20 +2629,20 @@ class TestRedisCommands: @skip_unless_arch_bits(64) @skip_if_server_version_lt('3.2.0') def test_georadiusmember(self, r): - values = (2.1909389952632, 41.433791470673, 'place1') +\ + values = (2.1909389952632, 41.433791470673, 'place1') + \ (2.1873744593677, 41.406342043777, b'\x80place2') r.geoadd('barcelona', *values) - assert r.georadiusbymember('barcelona', 'place1', 4000) ==\ - [b'\x80place2', b'place1'] + assert r.georadiusbymember('barcelona', 'place1', 4000) == \ + [b'\x80place2', b'place1'] assert r.georadiusbymember('barcelona', 'place1', 10) == [b'place1'] assert r.georadiusbymember('barcelona', 'place1', 4000, withdist=True, withcoord=True, - withhash=True) ==\ - [[b'\x80place2', 3067.4157, 3471609625421029, - (2.187376320362091, 41.40634178640635)], - [b'place1', 0.0, 3471609698139488, + withhash=True) == \ + [[b'\x80place2', 3067.4157, 3471609625421029, + (2.187376320362091, 41.40634178640635)], + [b'place1', 0.0, 3471609698139488, (2.1909382939338684, 41.433790281840835)]] @skip_if_server_version_lt('5.0.0') |