redis 5.3.0b5__py3-none-any.whl → 6.0.0b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. redis/__init__.py +2 -11
  2. redis/_parsers/base.py +14 -2
  3. redis/asyncio/client.py +20 -12
  4. redis/asyncio/cluster.py +79 -56
  5. redis/asyncio/connection.py +40 -11
  6. redis/asyncio/lock.py +26 -5
  7. redis/asyncio/sentinel.py +9 -1
  8. redis/asyncio/utils.py +1 -1
  9. redis/auth/token.py +6 -2
  10. redis/backoff.py +15 -0
  11. redis/client.py +21 -14
  12. redis/cluster.py +111 -49
  13. redis/commands/cluster.py +1 -11
  14. redis/commands/core.py +218 -206
  15. redis/commands/helpers.py +0 -70
  16. redis/commands/redismodules.py +0 -20
  17. redis/commands/search/aggregation.py +3 -1
  18. redis/commands/search/commands.py +41 -14
  19. redis/commands/search/dialect.py +3 -0
  20. redis/commands/search/profile_information.py +14 -0
  21. redis/commands/search/query.py +5 -1
  22. redis/connection.py +37 -19
  23. redis/exceptions.py +4 -1
  24. redis/lock.py +24 -4
  25. redis/ocsp.py +2 -1
  26. redis/sentinel.py +1 -1
  27. redis/utils.py +107 -1
  28. {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info}/METADATA +57 -23
  29. {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info}/RECORD +32 -39
  30. {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info}/WHEEL +1 -2
  31. redis/commands/graph/__init__.py +0 -263
  32. redis/commands/graph/commands.py +0 -313
  33. redis/commands/graph/edge.py +0 -91
  34. redis/commands/graph/exceptions.py +0 -3
  35. redis/commands/graph/execution_plan.py +0 -211
  36. redis/commands/graph/node.py +0 -88
  37. redis/commands/graph/path.py +0 -78
  38. redis/commands/graph/query_result.py +0 -588
  39. redis-5.3.0b5.dist-info/top_level.txt +0 -1
  40. /redis/commands/search/{indexDefinition.py → index_definition.py} +0 -0
  41. {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info/licenses}/LICENSE +0 -0
redis/cluster.py CHANGED
@@ -4,6 +4,7 @@ import sys
4
4
  import threading
5
5
  import time
6
6
  from collections import OrderedDict
7
+ from enum import Enum
7
8
  from typing import Any, Callable, Dict, List, Optional, Tuple, Union
8
9
 
9
10
  from redis._parsers import CommandsParser, Encoder
@@ -13,7 +14,7 @@ from redis.cache import CacheConfig, CacheFactory, CacheFactoryInterface, CacheI
13
14
  from redis.client import CaseInsensitiveDict, PubSub, Redis
14
15
  from redis.commands import READ_COMMANDS, RedisClusterCommands
15
16
  from redis.commands.helpers import list_or_args
16
- from redis.connection import ConnectionPool, DefaultParser, parse_url
17
+ from redis.connection import ConnectionPool, parse_url
17
18
  from redis.crc import REDIS_CLUSTER_HASH_SLOTS, key_slot
18
19
  from redis.event import (
19
20
  AfterPooledConnectionsInstantiationEvent,
@@ -24,12 +25,10 @@ from redis.event import (
24
25
  from redis.exceptions import (
25
26
  AskError,
26
27
  AuthenticationError,
27
- ClusterCrossSlotError,
28
28
  ClusterDownError,
29
29
  ClusterError,
30
30
  ConnectionError,
31
31
  DataError,
32
- MasterDownError,
33
32
  MovedError,
34
33
  RedisClusterException,
35
34
  RedisError,
@@ -42,6 +41,7 @@ from redis.lock import Lock
42
41
  from redis.retry import Retry
43
42
  from redis.utils import (
44
43
  HIREDIS_AVAILABLE,
44
+ deprecated_args,
45
45
  dict_merge,
46
46
  list_keys_to_dict,
47
47
  merge_result,
@@ -54,10 +54,13 @@ def get_node_name(host: str, port: Union[str, int]) -> str:
54
54
  return f"{host}:{port}"
55
55
 
56
56
 
57
+ @deprecated_args(
58
+ allowed_args=["redis_node"],
59
+ reason="Use get_connection(redis_node) instead",
60
+ version="5.0.3",
61
+ )
57
62
  def get_connection(redis_node, *args, **options):
58
- return redis_node.connection or redis_node.connection_pool.get_connection(
59
- args[0], **options
60
- )
63
+ return redis_node.connection or redis_node.connection_pool.get_connection()
61
64
 
62
65
 
63
66
  def parse_scan_result(command, res, **options):
@@ -193,20 +196,6 @@ def cleanup_kwargs(**kwargs):
193
196
  return connection_kwargs
194
197
 
195
198
 
196
- class ClusterParser(DefaultParser):
197
- EXCEPTION_CLASSES = dict_merge(
198
- DefaultParser.EXCEPTION_CLASSES,
199
- {
200
- "ASK": AskError,
201
- "TRYAGAIN": TryAgainError,
202
- "MOVED": MovedError,
203
- "CLUSTERDOWN": ClusterDownError,
204
- "CROSSSLOT": ClusterCrossSlotError,
205
- "MASTERDOWN": MasterDownError,
206
- },
207
- )
208
-
209
-
210
199
  class AbstractRedisCluster:
211
200
  RedisClusterRequestTTL = 16
212
201
 
@@ -300,7 +289,6 @@ class AbstractRedisCluster:
300
289
  "TFUNCTION LIST",
301
290
  "TFCALL",
302
291
  "TFCALLASYNC",
303
- "GRAPH.CONFIG",
304
292
  "LATENCY HISTORY",
305
293
  "LATENCY LATEST",
306
294
  "LATENCY RESET",
@@ -320,7 +308,6 @@ class AbstractRedisCluster:
320
308
  "FUNCTION LIST",
321
309
  "FUNCTION LOAD",
322
310
  "FUNCTION RESTORE",
323
- "REDISGEARS_2.REFRESHCLUSTER",
324
311
  "SCAN",
325
312
  "SCRIPT EXISTS",
326
313
  "SCRIPT FLUSH",
@@ -496,6 +483,11 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
496
483
  """
497
484
  return cls(url=url, **kwargs)
498
485
 
486
+ @deprecated_args(
487
+ args_to_warn=["read_from_replicas"],
488
+ reason="Please configure the 'load_balancing_strategy' instead",
489
+ version="5.0.3",
490
+ )
499
491
  def __init__(
500
492
  self,
501
493
  host: Optional[str] = None,
@@ -506,6 +498,7 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
506
498
  require_full_coverage: bool = False,
507
499
  reinitialize_steps: int = 5,
508
500
  read_from_replicas: bool = False,
501
+ load_balancing_strategy: Optional["LoadBalancingStrategy"] = None,
509
502
  dynamic_startup_nodes: bool = True,
510
503
  url: Optional[str] = None,
511
504
  address_remap: Optional[Callable[[Tuple[str, int]], Tuple[str, int]]] = None,
@@ -534,11 +527,16 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
534
527
  cluster client. If not all slots are covered, RedisClusterException
535
528
  will be thrown.
536
529
  :param read_from_replicas:
530
+ @deprecated - please use load_balancing_strategy instead
537
531
  Enable read from replicas in READONLY mode. You can read possibly
538
532
  stale data.
539
533
  When set to true, read commands will be assigned between the
540
534
  primary and its replications in a Round-Robin manner.
541
- :param dynamic_startup_nodes:
535
+ :param load_balancing_strategy:
536
+ Enable read from replicas in READONLY mode and defines the load balancing
537
+ strategy that will be used for cluster node selection.
538
+ The data read from replicas is eventually consistent with the data in primary nodes.
539
+ :param dynamic_startup_nodes:
542
540
  Set the RedisCluster's startup nodes to all of the discovered nodes.
543
541
  If true (default value), the cluster's discovered nodes will be used to
544
542
  determine the cluster nodes-slots mapping in the next topology refresh.
@@ -643,6 +641,7 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
643
641
  self.command_flags = self.__class__.COMMAND_FLAGS.copy()
644
642
  self.node_flags = self.__class__.NODE_FLAGS.copy()
645
643
  self.read_from_replicas = read_from_replicas
644
+ self.load_balancing_strategy = load_balancing_strategy
646
645
  self.reinitialize_counter = 0
647
646
  self.reinitialize_steps = reinitialize_steps
648
647
  if event_dispatcher is None:
@@ -676,7 +675,10 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
676
675
  self.close()
677
676
 
678
677
  def __del__(self):
679
- self.close()
678
+ try:
679
+ self.close()
680
+ except Exception:
681
+ pass
680
682
 
681
683
  def disconnect_connection_pools(self):
682
684
  for node in self.get_nodes():
@@ -692,10 +694,9 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
692
694
  Initialize the connection, authenticate and select a database and send
693
695
  READONLY if it is set during object initialization.
694
696
  """
695
- connection.set_parser(ClusterParser)
696
697
  connection.on_connect()
697
698
 
698
- if self.read_from_replicas:
699
+ if self.read_from_replicas or self.load_balancing_strategy:
699
700
  # Sending READONLY command to server to configure connection as
700
701
  # readonly. Since each cluster node may change its server type due
701
702
  # to a failover, we should establish a READONLY connection
@@ -822,6 +823,7 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
822
823
  cluster_response_callbacks=self.cluster_response_callbacks,
823
824
  cluster_error_retry_attempts=self.cluster_error_retry_attempts,
824
825
  read_from_replicas=self.read_from_replicas,
826
+ load_balancing_strategy=self.load_balancing_strategy,
825
827
  reinitialize_steps=self.reinitialize_steps,
826
828
  lock=self._lock,
827
829
  )
@@ -835,6 +837,7 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
835
837
  blocking_timeout=None,
836
838
  lock_class=None,
837
839
  thread_local=True,
840
+ raise_on_release_error: bool = True,
838
841
  ):
839
842
  """
840
843
  Return a new Lock object using key ``name`` that mimics
@@ -881,6 +884,11 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
881
884
  thread-1 would see the token value as "xyz" and would be
882
885
  able to successfully release the thread-2's lock.
883
886
 
887
+ ``raise_on_release_error`` indicates whether to raise an exception when
888
+ the lock is no longer owned when exiting the context manager. By default,
889
+ this is True, meaning an exception will be raised. If False, the warning
890
+ will be logged and the exception will be suppressed.
891
+
884
892
  In some use cases it's necessary to disable thread local storage. For
885
893
  example, if you have code where one thread acquires a lock and passes
886
894
  that lock instance to a worker thread to release later. If thread
@@ -898,6 +906,7 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
898
906
  blocking=blocking,
899
907
  blocking_timeout=blocking_timeout,
900
908
  thread_local=thread_local,
909
+ raise_on_release_error=raise_on_release_error,
901
910
  )
902
911
 
903
912
  def set_response_callback(self, command, callback):
@@ -939,7 +948,9 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
939
948
  # get the node that holds the key's slot
940
949
  slot = self.determine_slot(*args)
941
950
  node = self.nodes_manager.get_node_from_slot(
942
- slot, self.read_from_replicas and command in READ_COMMANDS
951
+ slot,
952
+ self.read_from_replicas and command in READ_COMMANDS,
953
+ self.load_balancing_strategy if command in READ_COMMANDS else None,
943
954
  )
944
955
  return [node]
945
956
 
@@ -1163,12 +1174,16 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
1163
1174
  # refresh the target node
1164
1175
  slot = self.determine_slot(*args)
1165
1176
  target_node = self.nodes_manager.get_node_from_slot(
1166
- slot, self.read_from_replicas and command in READ_COMMANDS
1177
+ slot,
1178
+ self.read_from_replicas and command in READ_COMMANDS,
1179
+ self.load_balancing_strategy
1180
+ if command in READ_COMMANDS
1181
+ else None,
1167
1182
  )
1168
1183
  moved = False
1169
1184
 
1170
1185
  redis_node = self.get_redis_connection(target_node)
1171
- connection = get_connection(redis_node, *args, **kwargs)
1186
+ connection = get_connection(redis_node)
1172
1187
  if asking:
1173
1188
  connection.send_command("ASKING")
1174
1189
  redis_node.parse_response(connection, "ASKING", **kwargs)
@@ -1312,6 +1327,12 @@ class ClusterNode:
1312
1327
  self.redis_connection.close()
1313
1328
 
1314
1329
 
1330
+ class LoadBalancingStrategy(Enum):
1331
+ ROUND_ROBIN = "round_robin"
1332
+ ROUND_ROBIN_REPLICAS = "round_robin_replicas"
1333
+ RANDOM_REPLICA = "random_replica"
1334
+
1335
+
1315
1336
  class LoadBalancer:
1316
1337
  """
1317
1338
  Round-Robin Load Balancing
@@ -1321,15 +1342,38 @@ class LoadBalancer:
1321
1342
  self.primary_to_idx = {}
1322
1343
  self.start_index = start_index
1323
1344
 
1324
- def get_server_index(self, primary: str, list_size: int) -> int:
1325
- server_index = self.primary_to_idx.setdefault(primary, self.start_index)
1326
- # Update the index
1327
- self.primary_to_idx[primary] = (server_index + 1) % list_size
1328
- return server_index
1345
+ def get_server_index(
1346
+ self,
1347
+ primary: str,
1348
+ list_size: int,
1349
+ load_balancing_strategy: LoadBalancingStrategy = LoadBalancingStrategy.ROUND_ROBIN,
1350
+ ) -> int:
1351
+ if load_balancing_strategy == LoadBalancingStrategy.RANDOM_REPLICA:
1352
+ return self._get_random_replica_index(list_size)
1353
+ else:
1354
+ return self._get_round_robin_index(
1355
+ primary,
1356
+ list_size,
1357
+ load_balancing_strategy == LoadBalancingStrategy.ROUND_ROBIN_REPLICAS,
1358
+ )
1329
1359
 
1330
1360
  def reset(self) -> None:
1331
1361
  self.primary_to_idx.clear()
1332
1362
 
1363
+ def _get_random_replica_index(self, list_size: int) -> int:
1364
+ return random.randint(1, list_size - 1)
1365
+
1366
+ def _get_round_robin_index(
1367
+ self, primary: str, list_size: int, replicas_only: bool
1368
+ ) -> int:
1369
+ server_index = self.primary_to_idx.setdefault(primary, self.start_index)
1370
+ if replicas_only and server_index == 0:
1371
+ # skip the primary node index
1372
+ server_index = 1
1373
+ # Update the index for the next round
1374
+ self.primary_to_idx[primary] = (server_index + 1) % list_size
1375
+ return server_index
1376
+
1333
1377
 
1334
1378
  class NodesManager:
1335
1379
  def __init__(
@@ -1433,7 +1477,21 @@ class NodesManager:
1433
1477
  # Reset moved_exception
1434
1478
  self._moved_exception = None
1435
1479
 
1436
- def get_node_from_slot(self, slot, read_from_replicas=False, server_type=None):
1480
+ @deprecated_args(
1481
+ args_to_warn=["server_type"],
1482
+ reason=(
1483
+ "In case you need select some load balancing strategy "
1484
+ "that will use replicas, please set it through 'load_balancing_strategy'"
1485
+ ),
1486
+ version="5.0.3",
1487
+ )
1488
+ def get_node_from_slot(
1489
+ self,
1490
+ slot,
1491
+ read_from_replicas=False,
1492
+ load_balancing_strategy=None,
1493
+ server_type=None,
1494
+ ):
1437
1495
  """
1438
1496
  Gets a node that servers this hash slot
1439
1497
  """
@@ -1448,11 +1506,14 @@ class NodesManager:
1448
1506
  f'"require_full_coverage={self._require_full_coverage}"'
1449
1507
  )
1450
1508
 
1451
- if read_from_replicas is True:
1452
- # get the server index in a Round-Robin manner
1509
+ if read_from_replicas is True and load_balancing_strategy is None:
1510
+ load_balancing_strategy = LoadBalancingStrategy.ROUND_ROBIN
1511
+
1512
+ if len(self.slots_cache[slot]) > 1 and load_balancing_strategy:
1513
+ # get the server index using the strategy defined in load_balancing_strategy
1453
1514
  primary_name = self.slots_cache[slot][0].name
1454
1515
  node_idx = self.read_load_balancer.get_server_index(
1455
- primary_name, len(self.slots_cache[slot])
1516
+ primary_name, len(self.slots_cache[slot]), load_balancing_strategy
1456
1517
  )
1457
1518
  elif (
1458
1519
  server_type is None
@@ -1641,7 +1702,7 @@ class NodesManager:
1641
1702
  if len(disagreements) > 5:
1642
1703
  raise RedisClusterException(
1643
1704
  f"startup_nodes could not agree on a valid "
1644
- f'slots cache: {", ".join(disagreements)}'
1705
+ f"slots cache: {', '.join(disagreements)}"
1645
1706
  )
1646
1707
 
1647
1708
  fully_covered = self.check_slots_coverage(tmp_slots)
@@ -1735,7 +1796,7 @@ class ClusterPubSub(PubSub):
1735
1796
  first command execution. The node will be determined by:
1736
1797
  1. Hashing the channel name in the request to find its keyslot
1737
1798
  2. Selecting a node that handles the keyslot: If read_from_replicas is
1738
- set to true, a replica can be selected.
1799
+ set to true or load_balancing_strategy is set, a replica can be selected.
1739
1800
 
1740
1801
  :type redis_cluster: RedisCluster
1741
1802
  :type node: ClusterNode
@@ -1831,7 +1892,9 @@ class ClusterPubSub(PubSub):
1831
1892
  channel = args[1]
1832
1893
  slot = self.cluster.keyslot(channel)
1833
1894
  node = self.cluster.nodes_manager.get_node_from_slot(
1834
- slot, self.cluster.read_from_replicas
1895
+ slot,
1896
+ self.cluster.read_from_replicas,
1897
+ self.cluster.load_balancing_strategy,
1835
1898
  )
1836
1899
  else:
1837
1900
  # Get a random node
@@ -1839,9 +1902,7 @@ class ClusterPubSub(PubSub):
1839
1902
  self.node = node
1840
1903
  redis_connection = self.cluster.get_redis_connection(node)
1841
1904
  self.connection_pool = redis_connection.connection_pool
1842
- self.connection = self.connection_pool.get_connection(
1843
- "pubsub", self.shard_hint
1844
- )
1905
+ self.connection = self.connection_pool.get_connection()
1845
1906
  # register a callback that re-subscribes to any channels we
1846
1907
  # were listening to when we were disconnected
1847
1908
  self.connection.register_connect_callback(self.on_connect)
@@ -1976,6 +2037,7 @@ class ClusterPipeline(RedisCluster):
1976
2037
  cluster_response_callbacks: Optional[Dict[str, Callable]] = None,
1977
2038
  startup_nodes: Optional[List["ClusterNode"]] = None,
1978
2039
  read_from_replicas: bool = False,
2040
+ load_balancing_strategy: Optional[LoadBalancingStrategy] = None,
1979
2041
  cluster_error_retry_attempts: int = 3,
1980
2042
  reinitialize_steps: int = 5,
1981
2043
  lock=None,
@@ -1991,6 +2053,7 @@ class ClusterPipeline(RedisCluster):
1991
2053
  )
1992
2054
  self.startup_nodes = startup_nodes if startup_nodes else []
1993
2055
  self.read_from_replicas = read_from_replicas
2056
+ self.load_balancing_strategy = load_balancing_strategy
1994
2057
  self.command_flags = self.__class__.COMMAND_FLAGS.copy()
1995
2058
  self.cluster_response_callbacks = cluster_response_callbacks
1996
2059
  self.cluster_error_retry_attempts = cluster_error_retry_attempts
@@ -2062,8 +2125,7 @@ class ClusterPipeline(RedisCluster):
2062
2125
  """
2063
2126
  cmd = " ".join(map(safe_str, command))
2064
2127
  msg = (
2065
- f"Command # {number} ({cmd}) of pipeline "
2066
- f"caused error: {exception.args[0]}"
2128
+ f"Command # {number} ({cmd}) of pipeline caused error: {exception.args[0]}"
2067
2129
  )
2068
2130
  exception.args = (msg,) + exception.args[1:]
2069
2131
 
@@ -2137,7 +2199,7 @@ class ClusterPipeline(RedisCluster):
2137
2199
  raise_on_error=raise_on_error,
2138
2200
  allow_redirections=allow_redirections,
2139
2201
  )
2140
- except (ClusterDownError, ConnectionError) as e:
2202
+ except RedisCluster.ERRORS_ALLOW_RETRY as e:
2141
2203
  if retry_attempts > 0:
2142
2204
  # Try again with the new cluster setup. All other errors
2143
2205
  # should be raised.
@@ -2201,8 +2263,8 @@ class ClusterPipeline(RedisCluster):
2201
2263
  if node_name not in nodes:
2202
2264
  redis_node = self.get_redis_connection(node)
2203
2265
  try:
2204
- connection = get_connection(redis_node, c.args)
2205
- except ConnectionError:
2266
+ connection = get_connection(redis_node)
2267
+ except (ConnectionError, TimeoutError):
2206
2268
  for n in nodes.values():
2207
2269
  n.connection_pool.release(n.connection)
2208
2270
  # Connection retries are being handled in the node's
redis/commands/cluster.py CHANGED
@@ -31,13 +31,11 @@ from .core import (
31
31
  AsyncACLCommands,
32
32
  AsyncDataAccessCommands,
33
33
  AsyncFunctionCommands,
34
- AsyncGearsCommands,
35
34
  AsyncManagementCommands,
36
35
  AsyncModuleCommands,
37
36
  AsyncScriptCommands,
38
37
  DataAccessCommands,
39
38
  FunctionCommands,
40
- GearsCommands,
41
39
  ManagementCommands,
42
40
  ModuleCommands,
43
41
  PubSubCommands,
@@ -595,7 +593,7 @@ class ClusterManagementCommands(ManagementCommands):
595
593
  "CLUSTER SETSLOT", slot_id, state, node_id, target_nodes=target_node
596
594
  )
597
595
  elif state.upper() == "STABLE":
598
- raise RedisError('For "stable" state please use ' "cluster_setslot_stable")
596
+ raise RedisError('For "stable" state please use cluster_setslot_stable')
599
597
  else:
600
598
  raise RedisError(f"Invalid slot state: {state}")
601
599
 
@@ -693,12 +691,6 @@ class ClusterManagementCommands(ManagementCommands):
693
691
  self.read_from_replicas = False
694
692
  return self.execute_command("READWRITE", target_nodes=target_nodes)
695
693
 
696
- def gears_refresh_cluster(self, **kwargs) -> ResponseT:
697
- """
698
- On an OSS cluster, before executing any gears function, you must call this command. # noqa
699
- """
700
- return self.execute_command("REDISGEARS_2.REFRESHCLUSTER", **kwargs)
701
-
702
694
 
703
695
  class AsyncClusterManagementCommands(
704
696
  ClusterManagementCommands, AsyncManagementCommands
@@ -874,7 +866,6 @@ class RedisClusterCommands(
874
866
  ClusterDataAccessCommands,
875
867
  ScriptCommands,
876
868
  FunctionCommands,
877
- GearsCommands,
878
869
  ModuleCommands,
879
870
  RedisModuleCommands,
880
871
  ):
@@ -905,7 +896,6 @@ class AsyncRedisClusterCommands(
905
896
  AsyncClusterDataAccessCommands,
906
897
  AsyncScriptCommands,
907
898
  AsyncFunctionCommands,
908
- AsyncGearsCommands,
909
899
  AsyncModuleCommands,
910
900
  AsyncRedisModuleCommands,
911
901
  ):