redis 5.3.0b4__py3-none-any.whl → 6.0.0__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 (51) hide show
  1. redis/__init__.py +2 -11
  2. redis/_parsers/base.py +14 -2
  3. redis/_parsers/resp3.py +2 -2
  4. redis/asyncio/client.py +103 -83
  5. redis/asyncio/cluster.py +147 -102
  6. redis/asyncio/connection.py +77 -24
  7. redis/asyncio/lock.py +26 -5
  8. redis/asyncio/retry.py +12 -0
  9. redis/asyncio/sentinel.py +11 -1
  10. redis/asyncio/utils.py +1 -1
  11. redis/auth/token.py +6 -2
  12. redis/backoff.py +15 -0
  13. redis/client.py +160 -138
  14. redis/cluster.py +211 -82
  15. redis/commands/cluster.py +1 -11
  16. redis/commands/core.py +219 -207
  17. redis/commands/helpers.py +19 -76
  18. redis/commands/json/__init__.py +1 -1
  19. redis/commands/redismodules.py +5 -17
  20. redis/commands/search/aggregation.py +3 -1
  21. redis/commands/search/commands.py +43 -16
  22. redis/commands/search/dialect.py +3 -0
  23. redis/commands/search/profile_information.py +14 -0
  24. redis/commands/search/query.py +5 -1
  25. redis/commands/timeseries/__init__.py +1 -1
  26. redis/commands/vectorset/__init__.py +46 -0
  27. redis/commands/vectorset/commands.py +367 -0
  28. redis/commands/vectorset/utils.py +94 -0
  29. redis/connection.py +89 -33
  30. redis/exceptions.py +4 -1
  31. redis/lock.py +24 -4
  32. redis/ocsp.py +2 -1
  33. redis/retry.py +12 -0
  34. redis/sentinel.py +3 -1
  35. redis/typing.py +1 -1
  36. redis/utils.py +114 -1
  37. {redis-5.3.0b4.dist-info → redis-6.0.0.dist-info}/METADATA +57 -23
  38. redis-6.0.0.dist-info/RECORD +78 -0
  39. {redis-5.3.0b4.dist-info → redis-6.0.0.dist-info}/WHEEL +1 -2
  40. redis/commands/graph/__init__.py +0 -263
  41. redis/commands/graph/commands.py +0 -313
  42. redis/commands/graph/edge.py +0 -91
  43. redis/commands/graph/exceptions.py +0 -3
  44. redis/commands/graph/execution_plan.py +0 -211
  45. redis/commands/graph/node.py +0 -88
  46. redis/commands/graph/path.py +0 -78
  47. redis/commands/graph/query_result.py +0 -588
  48. redis-5.3.0b4.dist-info/RECORD +0 -82
  49. redis-5.3.0b4.dist-info/top_level.txt +0 -1
  50. /redis/commands/search/{indexDefinition.py → index_definition.py} +0 -0
  51. {redis-5.3.0b4.dist-info → redis-6.0.0.dist-info/licenses}/LICENSE +0 -0
redis/asyncio/cluster.py CHANGED
@@ -2,7 +2,6 @@ import asyncio
2
2
  import collections
3
3
  import random
4
4
  import socket
5
- import ssl
6
5
  import warnings
7
6
  from typing import (
8
7
  Any,
@@ -26,11 +25,11 @@ from redis._parsers.helpers import (
26
25
  _RedisCallbacksRESP3,
27
26
  )
28
27
  from redis.asyncio.client import ResponseCallbackT
29
- from redis.asyncio.connection import Connection, DefaultParser, SSLConnection, parse_url
28
+ from redis.asyncio.connection import Connection, SSLConnection, parse_url
30
29
  from redis.asyncio.lock import Lock
31
30
  from redis.asyncio.retry import Retry
32
31
  from redis.auth.token import TokenInterface
33
- from redis.backoff import default_backoff
32
+ from redis.backoff import ExponentialWithJitterBackoff, NoBackoff
34
33
  from redis.client import EMPTY_RESPONSE, NEVER_DECODE, AbstractRedis
35
34
  from redis.cluster import (
36
35
  PIPELINE_BLOCKED_COMMANDS,
@@ -39,6 +38,7 @@ from redis.cluster import (
39
38
  SLOT_ID,
40
39
  AbstractRedisCluster,
41
40
  LoadBalancer,
41
+ LoadBalancingStrategy,
42
42
  block_pipeline_command,
43
43
  get_node_name,
44
44
  parse_cluster_slots,
@@ -50,12 +50,10 @@ from redis.event import AfterAsyncClusterInstantiationEvent, EventDispatcher
50
50
  from redis.exceptions import (
51
51
  AskError,
52
52
  BusyLoadingError,
53
- ClusterCrossSlotError,
54
53
  ClusterDownError,
55
54
  ClusterError,
56
55
  ConnectionError,
57
56
  DataError,
58
- MasterDownError,
59
57
  MaxConnectionsError,
60
58
  MovedError,
61
59
  RedisClusterException,
@@ -67,32 +65,26 @@ from redis.exceptions import (
67
65
  )
68
66
  from redis.typing import AnyKeyT, EncodableT, KeyT
69
67
  from redis.utils import (
68
+ SSL_AVAILABLE,
69
+ deprecated_args,
70
70
  deprecated_function,
71
- dict_merge,
72
71
  get_lib_version,
73
72
  safe_str,
74
73
  str_if_bytes,
74
+ truncate_text,
75
75
  )
76
76
 
77
+ if SSL_AVAILABLE:
78
+ from ssl import TLSVersion, VerifyMode
79
+ else:
80
+ TLSVersion = None
81
+ VerifyMode = None
82
+
77
83
  TargetNodesT = TypeVar(
78
84
  "TargetNodesT", str, "ClusterNode", List["ClusterNode"], Dict[Any, "ClusterNode"]
79
85
  )
80
86
 
81
87
 
82
- class ClusterParser(DefaultParser):
83
- EXCEPTION_CLASSES = dict_merge(
84
- DefaultParser.EXCEPTION_CLASSES,
85
- {
86
- "ASK": AskError,
87
- "CLUSTERDOWN": ClusterDownError,
88
- "CROSSSLOT": ClusterCrossSlotError,
89
- "MASTERDOWN": MasterDownError,
90
- "MOVED": MovedError,
91
- "TRYAGAIN": TryAgainError,
92
- },
93
- )
94
-
95
-
96
88
  class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommands):
97
89
  """
98
90
  Create a new RedisCluster client.
@@ -133,9 +125,15 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
133
125
  | See:
134
126
  https://redis.io/docs/manual/scaling/#redis-cluster-configuration-parameters
135
127
  :param read_from_replicas:
136
- | Enable read from replicas in READONLY mode. You can read possibly stale data.
128
+ | @deprecated - please use load_balancing_strategy instead
129
+ | Enable read from replicas in READONLY mode.
137
130
  When set to true, read commands will be assigned between the primary and
138
131
  its replications in a Round-Robin manner.
132
+ The data read from replicas is eventually consistent with the data in primary nodes.
133
+ :param load_balancing_strategy:
134
+ | Enable read from replicas in READONLY mode and defines the load balancing
135
+ strategy that will be used for cluster node selection.
136
+ The data read from replicas is eventually consistent with the data in primary nodes.
139
137
  :param reinitialize_steps:
140
138
  | Specifies the number of MOVED errors that need to occur before reinitializing
141
139
  the whole cluster topology. If a MOVED error occurs and the cluster does not
@@ -145,19 +143,23 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
145
143
  To avoid reinitializing the cluster on moved errors, set reinitialize_steps to
146
144
  0.
147
145
  :param cluster_error_retry_attempts:
148
- | Number of times to retry before raising an error when :class:`~.TimeoutError`
149
- or :class:`~.ConnectionError` or :class:`~.ClusterDownError` are encountered
150
- :param connection_error_retry_attempts:
151
- | Number of times to retry before reinitializing when :class:`~.TimeoutError`
152
- or :class:`~.ConnectionError` are encountered.
153
- The default backoff strategy will be set if Retry object is not passed (see
154
- default_backoff in backoff.py). To change it, pass a custom Retry object
155
- using the "retry" keyword.
146
+ | @deprecated - Please configure the 'retry' object instead
147
+ In case 'retry' object is set - this argument is ignored!
148
+
149
+ Number of times to retry before raising an error when :class:`~.TimeoutError`,
150
+ :class:`~.ConnectionError`, :class:`~.SlotNotCoveredError`
151
+ or :class:`~.ClusterDownError` are encountered
152
+ :param retry:
153
+ | A retry object that defines the retry strategy and the number of
154
+ retries for the cluster client.
155
+ In current implementation for the cluster client (starting form redis-py version 6.0.0)
156
+ the retry object is not yet fully utilized, instead it is used just to determine
157
+ the number of retries for the cluster client.
158
+ In the future releases the retry object will be used to handle the cluster client retries!
156
159
  :param max_connections:
157
160
  | Maximum number of connections per node. If there are no free connections & the
158
161
  maximum number of connections are already created, a
159
- :class:`~.MaxConnectionsError` is raised. This error may be retried as defined
160
- by :attr:`connection_error_retry_attempts`
162
+ :class:`~.MaxConnectionsError` is raised.
161
163
  :param address_remap:
162
164
  | An optional callable which, when provided with an internal network
163
165
  address of a node, e.g. a `(host, port)` tuple, will return the address
@@ -213,10 +215,9 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
213
215
  __slots__ = (
214
216
  "_initialize",
215
217
  "_lock",
216
- "cluster_error_retry_attempts",
218
+ "retry",
217
219
  "command_flags",
218
220
  "commands_parser",
219
- "connection_error_retry_attempts",
220
221
  "connection_kwargs",
221
222
  "encoder",
222
223
  "node_flags",
@@ -228,6 +229,18 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
228
229
  "result_callbacks",
229
230
  )
230
231
 
232
+ @deprecated_args(
233
+ args_to_warn=["read_from_replicas"],
234
+ reason="Please configure the 'load_balancing_strategy' instead",
235
+ version="5.3.0",
236
+ )
237
+ @deprecated_args(
238
+ args_to_warn=[
239
+ "cluster_error_retry_attempts",
240
+ ],
241
+ reason="Please configure the 'retry' object instead",
242
+ version="6.0.0",
243
+ )
231
244
  def __init__(
232
245
  self,
233
246
  host: Optional[str] = None,
@@ -236,10 +249,12 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
236
249
  startup_nodes: Optional[List["ClusterNode"]] = None,
237
250
  require_full_coverage: bool = True,
238
251
  read_from_replicas: bool = False,
252
+ load_balancing_strategy: Optional[LoadBalancingStrategy] = None,
239
253
  reinitialize_steps: int = 5,
240
254
  cluster_error_retry_attempts: int = 3,
241
- connection_error_retry_attempts: int = 3,
242
255
  max_connections: int = 2**31,
256
+ retry: Optional["Retry"] = None,
257
+ retry_on_error: Optional[List[Type[Exception]]] = None,
243
258
  # Client related kwargs
244
259
  db: Union[str, int] = 0,
245
260
  path: Optional[str] = None,
@@ -259,17 +274,15 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
259
274
  socket_keepalive: bool = False,
260
275
  socket_keepalive_options: Optional[Mapping[int, Union[int, bytes]]] = None,
261
276
  socket_timeout: Optional[float] = None,
262
- retry: Optional["Retry"] = None,
263
- retry_on_error: Optional[List[Type[Exception]]] = None,
264
277
  # SSL related kwargs
265
278
  ssl: bool = False,
266
279
  ssl_ca_certs: Optional[str] = None,
267
280
  ssl_ca_data: Optional[str] = None,
268
- ssl_cert_reqs: str = "required",
281
+ ssl_cert_reqs: Union[str, VerifyMode] = "required",
269
282
  ssl_certfile: Optional[str] = None,
270
- ssl_check_hostname: bool = False,
283
+ ssl_check_hostname: bool = True,
271
284
  ssl_keyfile: Optional[str] = None,
272
- ssl_min_version: Optional[ssl.TLSVersion] = None,
285
+ ssl_min_version: Optional[TLSVersion] = None,
273
286
  ssl_ciphers: Optional[str] = None,
274
287
  protocol: Optional[int] = 2,
275
288
  address_remap: Optional[Callable[[Tuple[str, int]], Tuple[str, int]]] = None,
@@ -297,7 +310,6 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
297
310
  kwargs: Dict[str, Any] = {
298
311
  "max_connections": max_connections,
299
312
  "connection_class": Connection,
300
- "parser_class": ClusterParser,
301
313
  # Client related kwargs
302
314
  "credential_provider": credential_provider,
303
315
  "username": username,
@@ -315,7 +327,6 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
315
327
  "socket_keepalive": socket_keepalive,
316
328
  "socket_keepalive_options": socket_keepalive_options,
317
329
  "socket_timeout": socket_timeout,
318
- "retry": retry,
319
330
  "protocol": protocol,
320
331
  }
321
332
 
@@ -335,21 +346,19 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
335
346
  }
336
347
  )
337
348
 
338
- if read_from_replicas:
349
+ if read_from_replicas or load_balancing_strategy:
339
350
  # Call our on_connect function to configure READONLY mode
340
351
  kwargs["redis_connect_func"] = self.on_connect
341
352
 
342
- self.retry = retry
343
- if retry or retry_on_error or connection_error_retry_attempts > 0:
344
- # Set a retry object for all cluster nodes
345
- self.retry = retry or Retry(
346
- default_backoff(), connection_error_retry_attempts
353
+ if retry:
354
+ self.retry = retry
355
+ else:
356
+ self.retry = Retry(
357
+ backoff=ExponentialWithJitterBackoff(base=1, cap=10),
358
+ retries=cluster_error_retry_attempts,
347
359
  )
348
- if not retry_on_error:
349
- # Default errors for retrying
350
- retry_on_error = [ConnectionError, TimeoutError]
360
+ if retry_on_error:
351
361
  self.retry.update_supported_errors(retry_on_error)
352
- kwargs.update({"retry": self.retry})
353
362
 
354
363
  kwargs["response_callbacks"] = _RedisCallbacks.copy()
355
364
  if kwargs.get("protocol") in ["3", 3]:
@@ -384,9 +393,8 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
384
393
  )
385
394
  self.encoder = Encoder(encoding, encoding_errors, decode_responses)
386
395
  self.read_from_replicas = read_from_replicas
396
+ self.load_balancing_strategy = load_balancing_strategy
387
397
  self.reinitialize_steps = reinitialize_steps
388
- self.cluster_error_retry_attempts = cluster_error_retry_attempts
389
- self.connection_error_retry_attempts = connection_error_retry_attempts
390
398
  self.reinitialize_counter = 0
391
399
  self.commands_parser = AsyncCommandsParser()
392
400
  self.node_flags = self.__class__.NODE_FLAGS.copy()
@@ -557,15 +565,8 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
557
565
  """Get the kwargs passed to :class:`~redis.asyncio.connection.Connection`."""
558
566
  return self.connection_kwargs
559
567
 
560
- def get_retry(self) -> Optional["Retry"]:
561
- return self.retry
562
-
563
- def set_retry(self, retry: "Retry") -> None:
568
+ def set_retry(self, retry: Retry) -> None:
564
569
  self.retry = retry
565
- for node in self.get_nodes():
566
- node.connection_kwargs.update({"retry": retry})
567
- for conn in node._connections:
568
- conn.retry = retry
569
570
 
570
571
  def set_response_callback(self, command: str, callback: ResponseCallbackT) -> None:
571
572
  """Set a custom response callback."""
@@ -602,6 +603,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
602
603
  self.nodes_manager.get_node_from_slot(
603
604
  await self._determine_slot(command, *args),
604
605
  self.read_from_replicas and command in READ_COMMANDS,
606
+ self.load_balancing_strategy if command in READ_COMMANDS else None,
605
607
  )
606
608
  ]
607
609
 
@@ -683,8 +685,8 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
683
685
  """
684
686
  Execute a raw command on the appropriate cluster node or target_nodes.
685
687
 
686
- It will retry the command as specified by :attr:`cluster_error_retry_attempts` &
687
- then raise an exception.
688
+ It will retry the command as specified by the retries property of
689
+ the :attr:`retry` & then raise an exception.
688
690
 
689
691
  :param args:
690
692
  | Raw command args
@@ -700,7 +702,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
700
702
  command = args[0]
701
703
  target_nodes = []
702
704
  target_nodes_specified = False
703
- retry_attempts = self.cluster_error_retry_attempts
705
+ retry_attempts = self.retry.get_retries()
704
706
 
705
707
  passed_targets = kwargs.pop("target_nodes", None)
706
708
  if passed_targets and not self._is_node_flag(passed_targets):
@@ -782,7 +784,11 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
782
784
  # refresh the target node
783
785
  slot = await self._determine_slot(*args)
784
786
  target_node = self.nodes_manager.get_node_from_slot(
785
- slot, self.read_from_replicas and args[0] in READ_COMMANDS
787
+ slot,
788
+ self.read_from_replicas and args[0] in READ_COMMANDS,
789
+ self.load_balancing_strategy
790
+ if args[0] in READ_COMMANDS
791
+ else None,
786
792
  )
787
793
  moved = False
788
794
 
@@ -799,10 +805,16 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
799
805
  # and try again with the new setup
800
806
  await self.aclose()
801
807
  raise
802
- except ClusterDownError:
808
+ except (ClusterDownError, SlotNotCoveredError):
803
809
  # ClusterDownError can occur during a failover and to get
804
810
  # self-healed, we will try to reinitialize the cluster layout
805
811
  # and retry executing the command
812
+
813
+ # SlotNotCoveredError can occur when the cluster is not fully
814
+ # initialized or can be temporary issue.
815
+ # We will try to reinitialize the cluster topology
816
+ # and retry executing the command
817
+
806
818
  await self.aclose()
807
819
  await asyncio.sleep(0.25)
808
820
  raise
@@ -862,6 +874,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
862
874
  blocking_timeout: Optional[float] = None,
863
875
  lock_class: Optional[Type[Lock]] = None,
864
876
  thread_local: bool = True,
877
+ raise_on_release_error: bool = True,
865
878
  ) -> Lock:
866
879
  """
867
880
  Return a new Lock object using key ``name`` that mimics
@@ -908,6 +921,11 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
908
921
  thread-1 would see the token value as "xyz" and would be
909
922
  able to successfully release the thread-2's lock.
910
923
 
924
+ ``raise_on_release_error`` indicates whether to raise an exception when
925
+ the lock is no longer owned when exiting the context manager. By default,
926
+ this is True, meaning an exception will be raised. If False, the warning
927
+ will be logged and the exception will be suppressed.
928
+
911
929
  In some use cases it's necessary to disable thread local storage. For
912
930
  example, if you have code where one thread acquires a lock and passes
913
931
  that lock instance to a worker thread to release later. If thread
@@ -925,6 +943,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
925
943
  blocking=blocking,
926
944
  blocking_timeout=blocking_timeout,
927
945
  thread_local=thread_local,
946
+ raise_on_release_error=raise_on_release_error,
928
947
  )
929
948
 
930
949
 
@@ -1026,7 +1045,23 @@ class ClusterNode:
1026
1045
  return self._free.popleft()
1027
1046
  except IndexError:
1028
1047
  if len(self._connections) < self.max_connections:
1029
- connection = self.connection_class(**self.connection_kwargs)
1048
+ # We are configuring the connection pool not to retry
1049
+ # connections on lower level clients to avoid retrying
1050
+ # connections to nodes that are not reachable
1051
+ # and to avoid blocking the connection pool.
1052
+ # The only error that will have some handling in the lower
1053
+ # level clients is ConnectionError which will trigger disconnection
1054
+ # of the socket.
1055
+ # The retries will be handled on cluster client level
1056
+ # where we will have proper handling of the cluster topology
1057
+ retry = Retry(
1058
+ backoff=NoBackoff(),
1059
+ retries=0,
1060
+ supported_errors=(ConnectionError,),
1061
+ )
1062
+ connection_kwargs = self.connection_kwargs.copy()
1063
+ connection_kwargs["retry"] = retry
1064
+ connection = self.connection_class(**connection_kwargs)
1030
1065
  self._connections.append(connection)
1031
1066
  return connection
1032
1067
 
@@ -1177,9 +1212,7 @@ class NodesManager:
1177
1212
  return self.nodes_cache.get(node_name)
1178
1213
  else:
1179
1214
  raise DataError(
1180
- "get_node requires one of the following: "
1181
- "1. node name "
1182
- "2. host and port"
1215
+ "get_node requires one of the following: 1. node name 2. host and port"
1183
1216
  )
1184
1217
 
1185
1218
  def set_nodes(
@@ -1239,17 +1272,23 @@ class NodesManager:
1239
1272
  self._moved_exception = None
1240
1273
 
1241
1274
  def get_node_from_slot(
1242
- self, slot: int, read_from_replicas: bool = False
1275
+ self,
1276
+ slot: int,
1277
+ read_from_replicas: bool = False,
1278
+ load_balancing_strategy=None,
1243
1279
  ) -> "ClusterNode":
1244
1280
  if self._moved_exception:
1245
1281
  self._update_moved_slots()
1246
1282
 
1283
+ if read_from_replicas is True and load_balancing_strategy is None:
1284
+ load_balancing_strategy = LoadBalancingStrategy.ROUND_ROBIN
1285
+
1247
1286
  try:
1248
- if read_from_replicas:
1249
- # get the server index in a Round-Robin manner
1287
+ if len(self.slots_cache[slot]) > 1 and load_balancing_strategy:
1288
+ # get the server index using the strategy defined in load_balancing_strategy
1250
1289
  primary_name = self.slots_cache[slot][0].name
1251
1290
  node_idx = self.read_load_balancer.get_server_index(
1252
- primary_name, len(self.slots_cache[slot])
1291
+ primary_name, len(self.slots_cache[slot]), load_balancing_strategy
1253
1292
  )
1254
1293
  return self.slots_cache[slot][node_idx]
1255
1294
  return self.slots_cache[slot][0]
@@ -1361,7 +1400,7 @@ class NodesManager:
1361
1400
  if len(disagreements) > 5:
1362
1401
  raise RedisClusterException(
1363
1402
  f"startup_nodes could not agree on a valid "
1364
- f'slots cache: {", ".join(disagreements)}'
1403
+ f"slots cache: {', '.join(disagreements)}"
1365
1404
  )
1366
1405
 
1367
1406
  # Validate if all slots are covered or if we should try next startup node
@@ -1518,7 +1557,7 @@ class ClusterPipeline(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterComm
1518
1557
  """
1519
1558
  Execute the pipeline.
1520
1559
 
1521
- It will retry the commands as specified by :attr:`cluster_error_retry_attempts`
1560
+ It will retry the commands as specified by retries specified in :attr:`retry`
1522
1561
  & then raise an exception.
1523
1562
 
1524
1563
  :param raise_on_error:
@@ -1534,29 +1573,28 @@ class ClusterPipeline(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterComm
1534
1573
  return []
1535
1574
 
1536
1575
  try:
1537
- for _ in range(self._client.cluster_error_retry_attempts):
1538
- if self._client._initialize:
1539
- await self._client.initialize()
1540
-
1576
+ retry_attempts = self._client.retry.get_retries()
1577
+ while True:
1541
1578
  try:
1579
+ if self._client._initialize:
1580
+ await self._client.initialize()
1542
1581
  return await self._execute(
1543
1582
  self._client,
1544
1583
  self._command_stack,
1545
1584
  raise_on_error=raise_on_error,
1546
1585
  allow_redirections=allow_redirections,
1547
1586
  )
1548
- except BaseException as e:
1549
- if type(e) in self.__class__.ERRORS_ALLOW_RETRY:
1550
- # Try again with the new cluster setup.
1551
- exception = e
1587
+
1588
+ except self.__class__.ERRORS_ALLOW_RETRY as e:
1589
+ if retry_attempts > 0:
1590
+ # Try again with the new cluster setup. All other errors
1591
+ # should be raised.
1592
+ retry_attempts -= 1
1552
1593
  await self._client.aclose()
1553
1594
  await asyncio.sleep(0.25)
1554
1595
  else:
1555
1596
  # All other errors should be raised.
1556
- raise
1557
-
1558
- # If it fails the configured number of times then raise an exception
1559
- raise exception
1597
+ raise e
1560
1598
  finally:
1561
1599
  self._command_stack = []
1562
1600
 
@@ -1616,24 +1654,31 @@ class ClusterPipeline(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterComm
1616
1654
  if isinstance(result, Exception):
1617
1655
  command = " ".join(map(safe_str, cmd.args))
1618
1656
  msg = (
1619
- f"Command # {cmd.position + 1} ({command}) of pipeline "
1620
- f"caused error: {result.args}"
1657
+ f"Command # {cmd.position + 1} "
1658
+ f"({truncate_text(command)}) "
1659
+ f"of pipeline caused error: {result.args}"
1621
1660
  )
1622
1661
  result.args = (msg,) + result.args[1:]
1623
1662
  raise result
1624
1663
 
1625
- default_node = nodes.get(client.get_default_node().name)
1626
- if default_node is not None:
1627
- # This pipeline execution used the default node, check if we need
1628
- # to replace it.
1629
- # Note: when the error is raised we'll reset the default node in the
1630
- # caller function.
1631
- for cmd in default_node[1]:
1632
- # Check if it has a command that failed with a relevant
1633
- # exception
1634
- if type(cmd.result) in self.__class__.ERRORS_ALLOW_RETRY:
1635
- client.replace_default_node()
1636
- break
1664
+ default_cluster_node = client.get_default_node()
1665
+
1666
+ # Check whether the default node was used. In some cases,
1667
+ # 'client.get_default_node()' may return None. The check below
1668
+ # prevents a potential AttributeError.
1669
+ if default_cluster_node is not None:
1670
+ default_node = nodes.get(default_cluster_node.name)
1671
+ if default_node is not None:
1672
+ # This pipeline execution used the default node, check if we need
1673
+ # to replace it.
1674
+ # Note: when the error is raised we'll reset the default node in the
1675
+ # caller function.
1676
+ for cmd in default_node[1]:
1677
+ # Check if it has a command that failed with a relevant
1678
+ # exception
1679
+ if type(cmd.result) in self.__class__.ERRORS_ALLOW_RETRY:
1680
+ client.replace_default_node()
1681
+ break
1637
1682
 
1638
1683
  return [cmd.result for cmd in stack]
1639
1684