Skip to content

Unit tests fixes for compatibility #1703

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

Merged
merged 12 commits into from
Nov 14, 2021
41 changes: 20 additions & 21 deletions redis/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -703,7 +703,6 @@ class Redis(RedisModuleCommands, CoreCommands, object):
'CLUSTER SET-CONFIG-EPOCH': bool_ok,
'CLUSTER SETSLOT': bool_ok,
'CLUSTER SLAVES': parse_cluster_nodes,
'COMMAND': int,
'COMMAND COUNT': int,
'CONFIG GET': parse_config_get,
'CONFIG RESETSTAT': bool_ok,
Expand Down Expand Up @@ -891,19 +890,25 @@ def __init__(self, host='localhost', port=6379,
self.response_callbacks = CaseInsensitiveDict(
self.__class__.RESPONSE_CALLBACKS)

# preload our class with the available redis commands
try:
self.__redis_commands__()
except RedisError:
pass

def __repr__(self):
return "%s<%s>" % (type(self).__name__, repr(self.connection_pool))

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

def load_external_module(self, modname, funcname, func):
def load_external_module(self, funcname, func,
):
"""
This function can be used to add externally defined redis modules,
and their namespaces to the redis client.
modname - A string containing the name of the redis module to look for
in the redis info block.

funcname - A string containing the name of the function to create
func - The function, being added to this class.

Expand All @@ -914,31 +919,25 @@ def load_external_module(self, modname, funcname, func):
from redis import Redis
from foomodule import F
r = Redis()
r.load_external_module("foomod", "foo", F)
r.load_external_module("foo", F)
r.foo().dothing('your', 'arguments')

For a concrete example see the reimport of the redisjson module in
tests/test_connection.py::test_loading_external_modules
"""
mods = self.loaded_modules
if modname.lower() not in mods:
raise ModuleError("{} is not loaded in redis.".format(modname))
setattr(self, funcname, func)

@property
def loaded_modules(self):
key = '__redis_modules__'
mods = getattr(self, key, None)
if mods is not None:
return mods

def __redis_commands__(self):
"""Store the list of available commands, for our redis instance."""
cmds = getattr(self, '__commands__', None)
if cmds is not None:
return cmds
try:
mods = {f.get('name').lower(): f.get('ver')
for f in self.info().get('modules')}
except TypeError:
mods = []
setattr(self, key, mods)
return mods
cmds = [c[0].upper().decode() for c in self.command()]
except AttributeError: # if encoded
cmds = [c[0].upper() for c in self.command()]
self.__commands__ = cmds
return cmds

def pipeline(self, transaction=True, shard_hint=None):
"""
Expand Down
3 changes: 3 additions & 0 deletions redis/commands/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3315,6 +3315,9 @@ def command_info(self):
def command_count(self):
return self.execute_command('COMMAND COUNT')

def command(self):
return self.execute_command('COMMAND')


class Script:
"An executable Lua script object returned by ``register_script``"
Expand Down
38 changes: 19 additions & 19 deletions redis/commands/redismodules.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,41 +8,41 @@ class RedisModuleCommands:
"""

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

from .json import JSON
jj = JSON(
client=self,
version=modversion,
encoder=encoder,
decoder=decoder)
return jj

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

from .search import Search
s = Search(client=self, version=modversion, index_name=index_name)
s = Search(client=self, index_name=index_name)
return s

def ts(self, index_name="idx"):
def ts(self):
"""Access the timeseries namespace, providing support for
redis timeseries data.
"""
try:
modversion = self.loaded_modules['timeseries']
except IndexError:
raise ModuleError("timeseries is not a loaded in "
"the redis instance.")
if 'TS.INFO' not in self.__commands__:
raise ModuleError("reditimeseries is not loaded in redis. "
"For more information visit "
"https://redistimeseries.io/")

from .timeseries import TimeSeries
s = TimeSeries(client=self, version=modversion, index_name=index_name)
s = TimeSeries(client=self)
return s
3 changes: 1 addition & 2 deletions redis/commands/search/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,15 +83,14 @@ def commit(self):
self.pipeline.execute()
self.current_chunk = 0

def __init__(self, client, version=None, index_name="idx"):
def __init__(self, client, index_name="idx"):
"""
Create a new Client for the given index_name.
The default name is `idx`

If conn is not None, we employ an already existing redis connection
"""
self.client = client
self.MODULE_VERSION = version
self.index_name = index_name
self.execute_command = client.execute_command
self.pipeline = client.pipeline
3 changes: 1 addition & 2 deletions redis/commands/timeseries/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class TimeSeries(TimeSeriesCommands):
functionality.
"""

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

self.client = client
self.execute_command = client.execute_command
self.MODULE_VERSION = version

for key, value in self.MODULE_CALLBACKS.items():
self.client.set_response_callback(key, value)
Expand Down
10 changes: 5 additions & 5 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,10 @@ def pytest_addoption(parser):
def _get_info(redis_url):
client = redis.Redis.from_url(redis_url)
info = client.info()
try:
client.execute_command("CONFIG SET maxmemory 5555555")
client.execute_command("CONFIG SET maxmemory 0")
info["enterprise"] = False
except redis.exceptions.ResponseError:
if 'dping' in client.__commands__:
info["enterprise"] = True
else:
info["enterprise"] = False
client.connection_pool.disconnect()
return info

Expand All @@ -57,6 +55,8 @@ def pytest_sessionstart(session):
REDIS_INFO["modules"] = info["modules"]
except redis.exceptions.ConnectionError:
pass
except KeyError:
pass


def skip_if_server_version_lt(min_version):
Expand Down
8 changes: 8 additions & 0 deletions tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -3671,6 +3671,14 @@ def test_command_count(self, r):
assert isinstance(res, int)
assert res >= 100

@skip_if_server_version_lt('2.8.13')
def test_command(self, r):
res = r.command()
assert len(res) >= 100
cmds = [c[0].decode() for c in res]
assert 'set' in cmds
assert 'get' in cmds

@skip_if_server_version_lt('4.0.0')
@skip_if_redis_enterprise
def test_module(self, r):
Expand Down
26 changes: 8 additions & 18 deletions tests/test_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import types
import pytest

from redis.exceptions import InvalidResponse, ModuleError
from redis.exceptions import InvalidResponse
from redis.utils import HIREDIS_AVAILABLE
from .conftest import skip_if_server_version_lt

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

@skip_if_server_version_lt('4.0.0')
@pytest.mark.redismod
def test_loaded_modules(r, modclient):
assert r.loaded_modules == []
assert 'rejson' in modclient.loaded_modules.keys()


@skip_if_server_version_lt('4.0.0')
@pytest.mark.redismod
def test_loading_external_modules(r, modclient):
def test_loading_external_modules(modclient):
def inner():
pass

with pytest.raises(ModuleError):
r.load_external_module('rejson', 'myfuncname', inner)

modclient.load_external_module('rejson', 'myfuncname', inner)
modclient.load_external_module('myfuncname', inner)
assert getattr(modclient, 'myfuncname') == inner
assert isinstance(getattr(modclient, 'myfuncname'), types.FunctionType)

# and call it
from redis.commands import RedisModuleCommands
j = RedisModuleCommands.json
modclient.load_external_module('rejson', 'sometestfuncname', j)
modclient.load_external_module('sometestfuncname', j)

d = {'hello': 'world!'}
mod = j(modclient)
mod.set("fookey", ".", d)
assert mod.get('fookey') == d
# d = {'hello': 'world!'}
# mod = j(modclient)
# mod.set("fookey", ".", d)
# assert mod.get('fookey') == d