diff options
author | Roey Prat <roey.prat@redislabs.com> | 2020-07-13 19:41:29 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-13 19:41:29 +0300 |
commit | cec8e77b510eae7720cdf7258deb0a0085a9af12 (patch) | |
tree | 172717a4f504e13d4990e4faf9819ee80640679d | |
parent | 10fb0c5814709fd6dca1fc666fbfd8b172fb08bc (diff) | |
download | redis-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-x | redis/client.py | 33 | ||||
-rwxr-xr-x | redis/connection.py | 13 | ||||
-rw-r--r-- | redis/exceptions.py | 4 | ||||
-rw-r--r-- | tests/test_commands.py | 5 |
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): |