Skip to content

Commit 8dfd9ba

Browse files
committed
Merge remote-tracking branch 'origin/master' into ck-explaincli
2 parents b45ce0a + c8cb715 commit 8dfd9ba

File tree

11 files changed

+96
-75
lines changed

11 files changed

+96
-75
lines changed

.github/release-drafter-config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ autolabeler:
1515
branch:
1616
- '/feature-.+'
1717
categories:
18-
- title: 'Breaking Changes'
18+
- title: '🔥 Breaking Changes'
1919
labels:
2020
- 'breakingchange'
2121
- title: '🚀 New Features'

redis/client.py

+20-21
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,6 @@ class Redis(RedisModuleCommands, CoreCommands, object):
703703
'CLUSTER SET-CONFIG-EPOCH': bool_ok,
704704
'CLUSTER SETSLOT': bool_ok,
705705
'CLUSTER SLAVES': parse_cluster_nodes,
706-
'COMMAND': int,
707706
'COMMAND COUNT': int,
708707
'CONFIG GET': parse_config_get,
709708
'CONFIG RESETSTAT': bool_ok,
@@ -891,19 +890,25 @@ def __init__(self, host='localhost', port=6379,
891890
self.response_callbacks = CaseInsensitiveDict(
892891
self.__class__.RESPONSE_CALLBACKS)
893892

893+
# preload our class with the available redis commands
894+
try:
895+
self.__redis_commands__()
896+
except RedisError:
897+
pass
898+
894899
def __repr__(self):
895900
return "%s<%s>" % (type(self).__name__, repr(self.connection_pool))
896901

897902
def set_response_callback(self, command, callback):
898903
"Set a custom Response Callback"
899904
self.response_callbacks[command] = callback
900905

901-
def load_external_module(self, modname, funcname, func):
906+
def load_external_module(self, funcname, func,
907+
):
902908
"""
903909
This function can be used to add externally defined redis modules,
904910
and their namespaces to the redis client.
905-
modname - A string containing the name of the redis module to look for
906-
in the redis info block.
911+
907912
funcname - A string containing the name of the function to create
908913
func - The function, being added to this class.
909914
@@ -914,31 +919,25 @@ def load_external_module(self, modname, funcname, func):
914919
from redis import Redis
915920
from foomodule import F
916921
r = Redis()
917-
r.load_external_module("foomod", "foo", F)
922+
r.load_external_module("foo", F)
918923
r.foo().dothing('your', 'arguments')
919924
920925
For a concrete example see the reimport of the redisjson module in
921926
tests/test_connection.py::test_loading_external_modules
922927
"""
923-
mods = self.loaded_modules
924-
if modname.lower() not in mods:
925-
raise ModuleError("{} is not loaded in redis.".format(modname))
926928
setattr(self, funcname, func)
927929

928-
@property
929-
def loaded_modules(self):
930-
key = '__redis_modules__'
931-
mods = getattr(self, key, None)
932-
if mods is not None:
933-
return mods
934-
930+
def __redis_commands__(self):
931+
"""Store the list of available commands, for our redis instance."""
932+
cmds = getattr(self, '__commands__', None)
933+
if cmds is not None:
934+
return cmds
935935
try:
936-
mods = {f.get('name').lower(): f.get('ver')
937-
for f in self.info().get('modules')}
938-
except TypeError:
939-
mods = []
940-
setattr(self, key, mods)
941-
return mods
936+
cmds = [c[0].upper().decode() for c in self.command()]
937+
except AttributeError: # if encoded
938+
cmds = [c[0].upper() for c in self.command()]
939+
self.__commands__ = cmds
940+
return cmds
942941

943942
def pipeline(self, transaction=True, shard_hint=None):
944943
"""

redis/commands/core.py

+3
Original file line numberDiff line numberDiff line change
@@ -3315,6 +3315,9 @@ def command_info(self):
33153315
def command_count(self):
33163316
return self.execute_command('COMMAND COUNT')
33173317

3318+
def command(self):
3319+
return self.execute_command('COMMAND')
3320+
33183321

33193322
class Script:
33203323
"An executable Lua script object returned by ``register_script``"

redis/commands/redismodules.py

+19-19
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,41 @@ class RedisModuleCommands:
88
"""
99

1010
def json(self, encoder=JSONEncoder(), decoder=JSONDecoder()):
11-
"""Access the json namespace, providing support for redis json."""
12-
try:
13-
modversion = self.loaded_modules['rejson']
14-
except IndexError:
15-
raise ModuleError("rejson is not a loaded in the redis instance.")
11+
"""Access the json namespace, providing support for redis json.
12+
"""
13+
if 'JSON.SET' not in self.__commands__:
14+
raise ModuleError("redisjson is not loaded in redis. "
15+
"For more information visit "
16+
"https://redisjson.io/")
1617

1718
from .json import JSON
1819
jj = JSON(
1920
client=self,
20-
version=modversion,
2121
encoder=encoder,
2222
decoder=decoder)
2323
return jj
2424

2525
def ft(self, index_name="idx"):
26-
"""Access the search namespace, providing support for redis search."""
27-
try:
28-
modversion = self.loaded_modules['search']
29-
except IndexError:
30-
raise ModuleError("search is not a loaded in the redis instance.")
26+
"""Access the search namespace, providing support for redis search.
27+
"""
28+
if 'FT.INFO' not in self.__commands__:
29+
raise ModuleError("redisearch is not loaded in redis. "
30+
"For more information visit "
31+
"https://redisearch.io/")
3132

3233
from .search import Search
33-
s = Search(client=self, version=modversion, index_name=index_name)
34+
s = Search(client=self, index_name=index_name)
3435
return s
3536

36-
def ts(self, index_name="idx"):
37+
def ts(self):
3738
"""Access the timeseries namespace, providing support for
3839
redis timeseries data.
3940
"""
40-
try:
41-
modversion = self.loaded_modules['timeseries']
42-
except IndexError:
43-
raise ModuleError("timeseries is not a loaded in "
44-
"the redis instance.")
41+
if 'TS.INFO' not in self.__commands__:
42+
raise ModuleError("reditimeseries is not loaded in redis. "
43+
"For more information visit "
44+
"https://redistimeseries.io/")
4545

4646
from .timeseries import TimeSeries
47-
s = TimeSeries(client=self, version=modversion, index_name=index_name)
47+
s = TimeSeries(client=self)
4848
return s

redis/commands/search/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -83,15 +83,14 @@ def commit(self):
8383
self.pipeline.execute()
8484
self.current_chunk = 0
8585

86-
def __init__(self, client, version=None, index_name="idx"):
86+
def __init__(self, client, index_name="idx"):
8787
"""
8888
Create a new Client for the given index_name.
8989
The default name is `idx`
9090
9191
If conn is not None, we employ an already existing redis connection
9292
"""
9393
self.client = client
94-
self.MODULE_VERSION = version
9594
self.index_name = index_name
9695
self.execute_command = client.execute_command
9796
self.pipeline = client.pipeline

redis/commands/timeseries/__init__.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ class TimeSeries(TimeSeriesCommands):
3434
functionality.
3535
"""
3636

37-
def __init__(self, client=None, version=None, **kwargs):
37+
def __init__(self, client=None, **kwargs):
3838
"""Create a new RedisTimeSeries client."""
3939
# Set the module commands' callbacks
4040
self.MODULE_CALLBACKS = {
@@ -55,7 +55,6 @@ def __init__(self, client=None, version=None, **kwargs):
5555

5656
self.client = client
5757
self.execute_command = client.execute_command
58-
self.MODULE_VERSION = version
5958

6059
for key, value in self.MODULE_CALLBACKS.items():
6160
self.client.set_response_callback(key, value)

redis/lock.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -76,14 +76,14 @@ def __init__(self, redis, name, timeout=None, sleep=0.1,
7676
Create a new Lock instance named ``name`` using the Redis client
7777
supplied by ``redis``.
7878
79-
``timeout`` indicates a maximum life for the lock.
79+
``timeout`` indicates a maximum life for the lock in seconds.
8080
By default, it will remain locked until release() is called.
8181
``timeout`` can be specified as a float or integer, both representing
8282
the number of seconds to wait.
8383
84-
``sleep`` indicates the amount of time to sleep per loop iteration
85-
when the lock is in blocking mode and another client is currently
86-
holding the lock.
84+
``sleep`` indicates the amount of time to sleep in seconds per loop
85+
iteration when the lock is in blocking mode and another client is
86+
currently holding the lock.
8787
8888
``blocking`` indicates whether calling ``acquire`` should block until
8989
the lock has been acquired or to fail immediately, causing ``acquire``

tests/conftest.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,10 @@ def pytest_addoption(parser):
3131
def _get_info(redis_url):
3232
client = redis.Redis.from_url(redis_url)
3333
info = client.info()
34-
try:
35-
client.execute_command("CONFIG SET maxmemory 5555555")
36-
client.execute_command("CONFIG SET maxmemory 0")
37-
info["enterprise"] = False
38-
except redis.exceptions.ResponseError:
34+
if 'dping' in client.__commands__:
3935
info["enterprise"] = True
36+
else:
37+
info["enterprise"] = False
4038
client.connection_pool.disconnect()
4139
return info
4240

@@ -57,6 +55,8 @@ def pytest_sessionstart(session):
5755
REDIS_INFO["modules"] = info["modules"]
5856
except redis.exceptions.ConnectionError:
5957
pass
58+
except KeyError:
59+
pass
6060

6161

6262
def skip_if_server_version_lt(min_version):

tests/test_commands.py

+31
Original file line numberDiff line numberDiff line change
@@ -1208,6 +1208,12 @@ def test_stralgo_lcs(self, r):
12081208
value1 = 'ohmytext'
12091209
value2 = 'mynewtext'
12101210
res = 'mytext'
1211+
1212+
if skip_if_redis_enterprise(None).args[0] is True:
1213+
with pytest.raises(redis.exceptions.ResponseError):
1214+
assert r.stralgo('LCS', value1, value2) == res
1215+
return
1216+
12111217
# test LCS of strings
12121218
assert r.stralgo('LCS', value1, value2) == res
12131219
# test using keys
@@ -1250,6 +1256,12 @@ def test_strlen(self, r):
12501256

12511257
def test_substr(self, r):
12521258
r['a'] = '0123456789'
1259+
1260+
if skip_if_redis_enterprise(None).args[0] is True:
1261+
with pytest.raises(redis.exceptions.ResponseError):
1262+
assert r.substr('a', 0) == b'0123456789'
1263+
return
1264+
12531265
assert r.substr('a', 0) == b'0123456789'
12541266
assert r.substr('a', 2) == b'23456789'
12551267
assert r.substr('a', 3, 5) == b'345'
@@ -3617,13 +3629,24 @@ def test_memory_doctor(self, r):
36173629

36183630
@skip_if_server_version_lt('4.0.0')
36193631
def test_memory_malloc_stats(self, r):
3632+
if skip_if_redis_enterprise(None).args[0] is True:
3633+
with pytest.raises(redis.exceptions.ResponseError):
3634+
assert r.memory_malloc_stats()
3635+
return
3636+
36203637
assert r.memory_malloc_stats()
36213638

36223639
@skip_if_server_version_lt('4.0.0')
36233640
def test_memory_stats(self, r):
36243641
# put a key into the current db to make sure that "db.<current-db>"
36253642
# has data
36263643
r.set('foo', 'bar')
3644+
3645+
if skip_if_redis_enterprise(None).args[0] is True:
3646+
with pytest.raises(redis.exceptions.ResponseError):
3647+
stats = r.memory_stats()
3648+
return
3649+
36273650
stats = r.memory_stats()
36283651
assert isinstance(stats, dict)
36293652
for key, value in stats.items():
@@ -3648,6 +3671,14 @@ def test_command_count(self, r):
36483671
assert isinstance(res, int)
36493672
assert res >= 100
36503673

3674+
@skip_if_server_version_lt('2.8.13')
3675+
def test_command(self, r):
3676+
res = r.command()
3677+
assert len(res) >= 100
3678+
cmds = [c[0].decode() for c in res]
3679+
assert 'set' in cmds
3680+
assert 'get' in cmds
3681+
36513682
@skip_if_server_version_lt('4.0.0')
36523683
@skip_if_redis_enterprise
36533684
def test_module(self, r):

tests/test_connection.py

+8-18
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import types
33
import pytest
44

5-
from redis.exceptions import InvalidResponse, ModuleError
5+
from redis.exceptions import InvalidResponse
66
from redis.utils import HIREDIS_AVAILABLE
77
from .conftest import skip_if_server_version_lt
88

@@ -19,30 +19,20 @@ def test_invalid_response(r):
1919

2020
@skip_if_server_version_lt('4.0.0')
2121
@pytest.mark.redismod
22-
def test_loaded_modules(r, modclient):
23-
assert r.loaded_modules == []
24-
assert 'rejson' in modclient.loaded_modules.keys()
25-
26-
27-
@skip_if_server_version_lt('4.0.0')
28-
@pytest.mark.redismod
29-
def test_loading_external_modules(r, modclient):
22+
def test_loading_external_modules(modclient):
3023
def inner():
3124
pass
3225

33-
with pytest.raises(ModuleError):
34-
r.load_external_module('rejson', 'myfuncname', inner)
35-
36-
modclient.load_external_module('rejson', 'myfuncname', inner)
26+
modclient.load_external_module('myfuncname', inner)
3727
assert getattr(modclient, 'myfuncname') == inner
3828
assert isinstance(getattr(modclient, 'myfuncname'), types.FunctionType)
3929

4030
# and call it
4131
from redis.commands import RedisModuleCommands
4232
j = RedisModuleCommands.json
43-
modclient.load_external_module('rejson', 'sometestfuncname', j)
33+
modclient.load_external_module('sometestfuncname', j)
4434

45-
d = {'hello': 'world!'}
46-
mod = j(modclient)
47-
mod.set("fookey", ".", d)
48-
assert mod.get('fookey') == d
35+
# d = {'hello': 'world!'}
36+
# mod = j(modclient)
37+
# mod.set("fookey", ".", d)
38+
# assert mod.get('fookey') == d

tests/test_search.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -649,9 +649,6 @@ def test_alias():
649649
index1 = getClient()
650650
index2 = getClient()
651651

652-
index1.hset("index1:lonestar", mapping={"name": "lonestar"})
653-
index2.hset("index2:yogurt", mapping={"name": "yogurt"})
654-
655652
def1 = IndexDefinition(prefix=["index1:"])
656653
def2 = IndexDefinition(prefix=["index2:"])
657654

@@ -660,6 +657,9 @@ def test_alias():
660657
ftindex1.create_index((TextField("name"),), definition=def1)
661658
ftindex2.create_index((TextField("name"),), definition=def2)
662659

660+
index1.hset("index1:lonestar", mapping={"name": "lonestar"})
661+
index2.hset("index2:yogurt", mapping={"name": "yogurt"})
662+
663663
res = ftindex1.search("*").docs[0]
664664
assert "index1:lonestar" == res.id
665665

0 commit comments

Comments
 (0)