summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRoey Prat <roey.prat@redislabs.com>2020-07-13 19:41:29 +0300
committerGitHub <noreply@github.com>2020-07-13 19:41:29 +0300
commitcec8e77b510eae7720cdf7258deb0a0085a9af12 (patch)
tree172717a4f504e13d4990e4faf9819ee80640679d
parent10fb0c5814709fd6dca1fc666fbfd8b172fb08bc (diff)
downloadredis-py-cec8e77b510eae7720cdf7258deb0a0085a9af12.tar.gz
Support for loading, unloading and listing Redis Modules (#1360)
* Support for loading, unloading and listing Redis Modules * minor fixes for flake * unit test for module list - only the empty use case * ModuleError should inherit from ResponseError rather than RedisError Co-authored-by: Vamsi Atluri <vamc19@gmail.com>
-rwxr-xr-xredis/client.py33
-rwxr-xr-xredis/connection.py13
-rw-r--r--redis/exceptions.py4
-rw-r--r--tests/test_commands.py5
4 files changed, 55 insertions, 0 deletions
diff --git a/redis/client.py b/redis/client.py
index 9de90fb..9653f7d 100755
--- a/redis/client.py
+++ b/redis/client.py
@@ -22,6 +22,7 @@ from redis.exceptions import (
ResponseError,
TimeoutError,
WatchError,
+ ModuleError,
)
SYM_EMPTY = b''
@@ -516,6 +517,12 @@ def parse_acl_getuser(response, **options):
return data
+def parse_module_result(response):
+ if isinstance(response, ModuleError):
+ raise response
+ return True
+
+
class Redis(object):
"""
Implementation of the Redis protocol.
@@ -656,6 +663,10 @@ class Redis(object):
'XPENDING': parse_xpending,
'ZADD': parse_zadd,
'ZSCAN': parse_zscan,
+ 'MODULE LOAD': parse_module_result,
+ 'MODULE UNLOAD': parse_module_result,
+ 'MODULE LIST': lambda response: [pairs_to_dict(module)
+ for module in response],
}
)
@@ -3306,6 +3317,28 @@ class Redis(object):
return self.execute_command(command, *pieces, **kwargs)
+ # MODULE COMMANDS
+ def module_load(self, path):
+ """
+ Loads the module from ``path``.
+ Raises ``ModuleError`` if a module is not found at ``path``.
+ """
+ return self.execute_command('MODULE LOAD', path)
+
+ def module_unload(self, name):
+ """
+ Unloads the module ``name``.
+ Raises ``ModuleError`` if ``name`` is not in loaded modules.
+ """
+ return self.execute_command('MODULE UNLOAD', name)
+
+ def module_list(self):
+ """
+ Returns a list of dictionaries containing the name and version of
+ all loaded modules.
+ """
+ return self.execute_command('MODULE LIST')
+
StrictRedis = Redis
diff --git a/redis/connection.py b/redis/connection.py
index e3c9b66..b08aee9 100755
--- a/redis/connection.py
+++ b/redis/connection.py
@@ -29,6 +29,7 @@ from redis.exceptions import (
RedisError,
ResponseError,
TimeoutError,
+ ModuleError,
)
from redis.utils import HIREDIS_AVAILABLE
@@ -90,6 +91,14 @@ SYM_EMPTY = b''
SERVER_CLOSED_CONNECTION_ERROR = "Connection closed by server."
SENTINEL = object()
+MODULE_LOAD_ERROR = 'Error loading the extension. ' \
+ 'Please check the server logs.'
+NO_SUCH_MODULE_ERROR = 'Error unloading module: no such module with that name'
+MODULE_UNLOAD_NOT_POSSIBLE_ERROR = 'Error unloading module: operation not ' \
+ 'possible.'
+MODULE_EXPORTS_DATA_TYPES_ERROR = "Error unloading module: the module " \
+ "exports one or more module-side data " \
+ "types, can't unload"
class Encoder(object):
@@ -146,6 +155,10 @@ class BaseParser(object):
# in uppercase
'wrong number of arguments for \'AUTH\' command':
AuthenticationWrongNumberOfArgsError,
+ MODULE_LOAD_ERROR: ModuleError,
+ MODULE_EXPORTS_DATA_TYPES_ERROR: ModuleError,
+ NO_SUCH_MODULE_ERROR: ModuleError,
+ MODULE_UNLOAD_NOT_POSSIBLE_ERROR: ModuleError,
},
'EXECABORT': ExecAbortError,
'LOADING': BusyLoadingError,
diff --git a/redis/exceptions.py b/redis/exceptions.py
index 760af66..ca2ad87 100644
--- a/redis/exceptions.py
+++ b/redis/exceptions.py
@@ -57,6 +57,10 @@ class NoPermissionError(ResponseError):
pass
+class ModuleError(ResponseError):
+ pass
+
+
class LockError(RedisError, ValueError):
"Errors acquiring or releasing a lock"
# NOTE: For backwards compatability, this class derives from ValueError.
diff --git a/tests/test_commands.py b/tests/test_commands.py
index 65e877c..adaa9fc 100644
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -2629,6 +2629,11 @@ class TestRedisCommands(object):
r.set('foo', 'bar')
assert isinstance(r.memory_usage('foo'), int)
+ @skip_if_server_version_lt('4.0.0')
+ def test_module_list(self, r):
+ assert isinstance(r.module_list(), list)
+ assert not r.module_list()
+
class TestBinarySave(object):