Repository: redis/redis-py

Issue:
redis cluster client throws error on calling module_list()
**Version**: What redis-py and what redis version is the issue happening on?

redis_py 4.6.0
Server 6.0.16 (Debian 11 package) and newer (like 6.2 or 7.0)- Server must be run in Cluster mode

**Platform**: What platform / version? (For example Python 3.5.1 on Windows 7 / Ubuntu 15.10 / Azure)
python 3.11 on Debian 12

**Description**: Description of your issue, stack traces from errors and code that reproduces the issue

Creating an "normal" Redis client i am able to call the method `module_list()` on the client and get an list response (either empty or filled, depending  on the server and modules installed there. Its working fine.

Same is true for an Client created from a Redis Sentinel based connection. Calling `module_list()` gives a list response as expected.

Now - whenever i create a Redis Cluster client the method cannot be called anymore, the client throws an exception instead of returning the list with all modules installed.

Example oth the error:
```
from redis.cluster import RedisCluster as Redis
client = Redis.from_url(redis_url)

client.module_list()
# ==> {AttributeError}'RedisCluster' object has no attribute 'module_list'

client.execute_command('module', 'list')
# ==> {RedisClusterException}No way to dispatch this command to Redis Cluster. Missing key.
#        You can execute the command by specifying target nodes.
#        Command: ('module', 'list')
```


Working code - tested against the same server version. With the regular client i can connect to one cluster node directly also and call `module_list()` there too without problem (but not the get() and similar methods - there i get the "MOVED" answers as expected for cluster nodes):
```
import redis
client = redis.from_url(redis_url)
client.module_list()
# ==> list with modules
```
```
import redis
sentinel = redis.sentinel.Sentinel(sentinel_list)
client = sentinel.master_for('mymaster')
client.module_list()
# ==> list with modules
```

As the Cluster Redis client is expected to be a drop in replacement for most client actions the "module" methods should all be available there too.

Thanks in advance for looking into it


Discussion:

Resolving patch:
diff --git a/redis/cluster.py b/redis/cluster.py
--- a/redis/cluster.py
+++ b/redis/cluster.py
@@ -295,6 +295,10 @@ class AbstractRedisCluster:
                 "LATENCY HISTORY",
                 "LATENCY LATEST",
                 "LATENCY RESET",
+                "MODULE LIST",
+                "MODULE LOAD",
+                "MODULE UNLOAD",
+                "MODULE LOADEX",
             ],
             DEFAULT_NODE,
         ),
diff --git a/redis/commands/cluster.py b/redis/commands/cluster.py
--- a/redis/commands/cluster.py
+++ b/redis/commands/cluster.py
@@ -32,11 +32,13 @@
     AsyncFunctionCommands,
     AsyncGearsCommands,
     AsyncManagementCommands,
+    AsyncModuleCommands,
     AsyncScriptCommands,
     DataAccessCommands,
     FunctionCommands,
     GearsCommands,
     ManagementCommands,
+    ModuleCommands,
     PubSubCommands,
     ResponseT,
     ScriptCommands,
@@ -873,6 +875,7 @@ class RedisClusterCommands(
     ScriptCommands,
     FunctionCommands,
     GearsCommands,
+    ModuleCommands,
     RedisModuleCommands,
 ):
     """
@@ -903,6 +906,7 @@ class AsyncRedisClusterCommands(
     AsyncScriptCommands,
     AsyncFunctionCommands,
     AsyncGearsCommands,
+    AsyncModuleCommands,
 ):
     """
     A class for all Redis Cluster commands


Test patch:
diff --git a/tests/test_asyncio/test_commands.py b/tests/test_asyncio/test_commands.py
--- a/tests/test_asyncio/test_commands.py
+++ b/tests/test_asyncio/test_commands.py
@@ -3213,7 +3213,6 @@ async def test_memory_usage(self, r: redis.Redis):
         assert isinstance(await r.memory_usage("foo"), int)
 
     @skip_if_server_version_lt("4.0.0")
-    @pytest.mark.onlynoncluster
     async def test_module_list(self, r: redis.Redis):
         assert isinstance(await r.module_list(), list)
         for x in await r.module_list():
diff --git a/tests/test_commands.py b/tests/test_commands.py
--- a/tests/test_commands.py
+++ b/tests/test_commands.py
@@ -4909,7 +4909,6 @@ def test_latency_latest(self, r: redis.Redis):
     def test_latency_reset(self, r: redis.Redis):
         assert r.latency_reset() == 0
 
-    @pytest.mark.onlynoncluster
     @skip_if_server_version_lt("4.0.0")
     @skip_if_redis_enterprise()
     def test_module_list(self, r):