redis 5.3.0b4__tar.gz → 5.3.1__tar.gz

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 (160) hide show
  1. {redis-5.3.0b4/redis.egg-info → redis-5.3.1}/PKG-INFO +1 -1
  2. {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/client.py +7 -11
  3. {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/cluster.py +47 -12
  4. {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/connection.py +13 -3
  5. {redis-5.3.0b4 → redis-5.3.1}/redis/backoff.py +15 -0
  6. {redis-5.3.0b4 → redis-5.3.1}/redis/client.py +65 -53
  7. {redis-5.3.0b4 → redis-5.3.1}/redis/cluster.py +118 -33
  8. {redis-5.3.0b4 → redis-5.3.1}/redis/connection.py +30 -11
  9. {redis-5.3.0b4 → redis-5.3.1}/redis/typing.py +1 -1
  10. {redis-5.3.0b4 → redis-5.3.1}/redis/utils.py +65 -0
  11. {redis-5.3.0b4 → redis-5.3.1/redis.egg-info}/PKG-INFO +1 -1
  12. {redis-5.3.0b4 → redis-5.3.1}/redis.egg-info/SOURCES.txt +1 -0
  13. {redis-5.3.0b4 → redis-5.3.1}/redis.egg-info/requires.txt +1 -1
  14. {redis-5.3.0b4 → redis-5.3.1}/setup.py +2 -2
  15. {redis-5.3.0b4 → redis-5.3.1}/tests/conftest.py +62 -37
  16. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/conftest.py +61 -36
  17. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_cluster.py +137 -14
  18. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_connection.py +15 -17
  19. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_connection_pool.py +32 -32
  20. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_credentials.py +1 -1
  21. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_encoding.py +1 -1
  22. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_retry.py +2 -2
  23. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_sentinel.py +1 -1
  24. {redis-5.3.0b4 → redis-5.3.1}/tests/test_auth/test_token_manager.py +15 -21
  25. redis-5.3.1/tests/test_backoff.py +17 -0
  26. {redis-5.3.0b4 → redis-5.3.1}/tests/test_cache.py +3 -3
  27. {redis-5.3.0b4 → redis-5.3.1}/tests/test_cluster.py +157 -18
  28. {redis-5.3.0b4 → redis-5.3.1}/tests/test_commands.py +1 -1
  29. {redis-5.3.0b4 → redis-5.3.1}/tests/test_connection.py +10 -15
  30. {redis-5.3.0b4 → redis-5.3.1}/tests/test_connection_pool.py +68 -33
  31. {redis-5.3.0b4 → redis-5.3.1}/tests/test_credentials.py +1 -1
  32. {redis-5.3.0b4 → redis-5.3.1}/tests/test_multiprocessing.py +43 -5
  33. {redis-5.3.0b4 → redis-5.3.1}/tests/test_retry.py +2 -2
  34. {redis-5.3.0b4 → redis-5.3.1}/tests/test_sentinel.py +1 -1
  35. {redis-5.3.0b4 → redis-5.3.1}/INSTALL +0 -0
  36. {redis-5.3.0b4 → redis-5.3.1}/LICENSE +0 -0
  37. {redis-5.3.0b4 → redis-5.3.1}/MANIFEST.in +0 -0
  38. {redis-5.3.0b4 → redis-5.3.1}/README.md +0 -0
  39. {redis-5.3.0b4 → redis-5.3.1}/redis/__init__.py +0 -0
  40. {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/__init__.py +0 -0
  41. {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/base.py +0 -0
  42. {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/commands.py +0 -0
  43. {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/encoders.py +0 -0
  44. {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/helpers.py +0 -0
  45. {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/hiredis.py +0 -0
  46. {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/resp2.py +0 -0
  47. {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/resp3.py +0 -0
  48. {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/socket.py +0 -0
  49. {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/__init__.py +0 -0
  50. {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/lock.py +0 -0
  51. {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/retry.py +0 -0
  52. {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/sentinel.py +0 -0
  53. {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/utils.py +0 -0
  54. {redis-5.3.0b4 → redis-5.3.1}/redis/auth/__init__.py +0 -0
  55. {redis-5.3.0b4 → redis-5.3.1}/redis/auth/err.py +0 -0
  56. {redis-5.3.0b4 → redis-5.3.1}/redis/auth/idp.py +0 -0
  57. {redis-5.3.0b4 → redis-5.3.1}/redis/auth/token.py +0 -0
  58. {redis-5.3.0b4 → redis-5.3.1}/redis/auth/token_manager.py +0 -0
  59. {redis-5.3.0b4 → redis-5.3.1}/redis/cache.py +0 -0
  60. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/__init__.py +0 -0
  61. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/bf/__init__.py +0 -0
  62. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/bf/commands.py +0 -0
  63. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/bf/info.py +0 -0
  64. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/cluster.py +0 -0
  65. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/core.py +0 -0
  66. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/__init__.py +0 -0
  67. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/commands.py +0 -0
  68. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/edge.py +0 -0
  69. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/exceptions.py +0 -0
  70. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/execution_plan.py +0 -0
  71. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/node.py +0 -0
  72. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/path.py +0 -0
  73. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/query_result.py +0 -0
  74. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/helpers.py +0 -0
  75. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/json/__init__.py +0 -0
  76. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/json/_util.py +0 -0
  77. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/json/commands.py +0 -0
  78. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/json/decoders.py +0 -0
  79. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/json/path.py +0 -0
  80. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/redismodules.py +0 -0
  81. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/__init__.py +0 -0
  82. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/_util.py +0 -0
  83. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/aggregation.py +0 -0
  84. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/commands.py +0 -0
  85. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/document.py +0 -0
  86. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/field.py +0 -0
  87. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/indexDefinition.py +0 -0
  88. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/query.py +0 -0
  89. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/querystring.py +0 -0
  90. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/reducers.py +0 -0
  91. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/result.py +0 -0
  92. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/suggestion.py +0 -0
  93. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/sentinel.py +0 -0
  94. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/timeseries/__init__.py +0 -0
  95. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/timeseries/commands.py +0 -0
  96. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/timeseries/info.py +0 -0
  97. {redis-5.3.0b4 → redis-5.3.1}/redis/commands/timeseries/utils.py +0 -0
  98. {redis-5.3.0b4 → redis-5.3.1}/redis/crc.py +0 -0
  99. {redis-5.3.0b4 → redis-5.3.1}/redis/credentials.py +0 -0
  100. {redis-5.3.0b4 → redis-5.3.1}/redis/event.py +0 -0
  101. {redis-5.3.0b4 → redis-5.3.1}/redis/exceptions.py +0 -0
  102. {redis-5.3.0b4 → redis-5.3.1}/redis/lock.py +0 -0
  103. {redis-5.3.0b4 → redis-5.3.1}/redis/ocsp.py +0 -0
  104. {redis-5.3.0b4 → redis-5.3.1}/redis/retry.py +0 -0
  105. {redis-5.3.0b4 → redis-5.3.1}/redis/sentinel.py +0 -0
  106. {redis-5.3.0b4 → redis-5.3.1}/redis.egg-info/dependency_links.txt +0 -0
  107. {redis-5.3.0b4 → redis-5.3.1}/redis.egg-info/top_level.txt +0 -0
  108. {redis-5.3.0b4 → redis-5.3.1}/setup.cfg +0 -0
  109. {redis-5.3.0b4 → redis-5.3.1}/tests/__init__.py +0 -0
  110. {redis-5.3.0b4 → redis-5.3.1}/tests/mocks.py +0 -0
  111. {redis-5.3.0b4 → redis-5.3.1}/tests/ssl_utils.py +0 -0
  112. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/__init__.py +0 -0
  113. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/compat.py +0 -0
  114. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/mocks.py +0 -0
  115. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_bloom.py +0 -0
  116. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_commands.py +0 -0
  117. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_connect.py +0 -0
  118. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_cwe_404.py +0 -0
  119. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_graph.py +0 -0
  120. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_hash.py +0 -0
  121. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_json.py +0 -0
  122. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_lock.py +0 -0
  123. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_monitor.py +0 -0
  124. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_pipeline.py +0 -0
  125. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_pubsub.py +0 -0
  126. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_scripting.py +0 -0
  127. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_search.py +0 -0
  128. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_sentinel_managed_connection.py +0 -0
  129. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_timeseries.py +0 -0
  130. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/testdata/jsontestdata.py +0 -0
  131. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/testdata/titles.csv +0 -0
  132. {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/testdata/will_play_text.csv.bz2 +0 -0
  133. {redis-5.3.0b4 → redis-5.3.1}/tests/test_auth/__init__.py +0 -0
  134. {redis-5.3.0b4 → redis-5.3.1}/tests/test_auth/test_token.py +0 -0
  135. {redis-5.3.0b4 → redis-5.3.1}/tests/test_bloom.py +0 -0
  136. {redis-5.3.0b4 → redis-5.3.1}/tests/test_command_parser.py +0 -0
  137. {redis-5.3.0b4 → redis-5.3.1}/tests/test_connect.py +0 -0
  138. {redis-5.3.0b4 → redis-5.3.1}/tests/test_encoding.py +0 -0
  139. {redis-5.3.0b4 → redis-5.3.1}/tests/test_function.py +0 -0
  140. {redis-5.3.0b4 → redis-5.3.1}/tests/test_graph.py +0 -0
  141. {redis-5.3.0b4 → redis-5.3.1}/tests/test_graph_utils/__init__.py +0 -0
  142. {redis-5.3.0b4 → redis-5.3.1}/tests/test_graph_utils/test_edge.py +0 -0
  143. {redis-5.3.0b4 → redis-5.3.1}/tests/test_graph_utils/test_node.py +0 -0
  144. {redis-5.3.0b4 → redis-5.3.1}/tests/test_graph_utils/test_path.py +0 -0
  145. {redis-5.3.0b4 → redis-5.3.1}/tests/test_hash.py +0 -0
  146. {redis-5.3.0b4 → redis-5.3.1}/tests/test_helpers.py +0 -0
  147. {redis-5.3.0b4 → redis-5.3.1}/tests/test_json.py +0 -0
  148. {redis-5.3.0b4 → redis-5.3.1}/tests/test_lock.py +0 -0
  149. {redis-5.3.0b4 → redis-5.3.1}/tests/test_monitor.py +0 -0
  150. {redis-5.3.0b4 → redis-5.3.1}/tests/test_parsers/test_helpers.py +0 -0
  151. {redis-5.3.0b4 → redis-5.3.1}/tests/test_pipeline.py +0 -0
  152. {redis-5.3.0b4 → redis-5.3.1}/tests/test_pubsub.py +0 -0
  153. {redis-5.3.0b4 → redis-5.3.1}/tests/test_scripting.py +0 -0
  154. {redis-5.3.0b4 → redis-5.3.1}/tests/test_search.py +0 -0
  155. {redis-5.3.0b4 → redis-5.3.1}/tests/test_ssl.py +0 -0
  156. {redis-5.3.0b4 → redis-5.3.1}/tests/test_timeseries.py +0 -0
  157. {redis-5.3.0b4 → redis-5.3.1}/tests/test_utils.py +0 -0
  158. {redis-5.3.0b4 → redis-5.3.1}/tests/testdata/jsontestdata.py +0 -0
  159. {redis-5.3.0b4 → redis-5.3.1}/tests/testdata/titles.csv +0 -0
  160. {redis-5.3.0b4 → redis-5.3.1}/tests/testdata/will_play_text.csv.bz2 +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: redis
3
- Version: 5.3.0b4
3
+ Version: 5.3.1
4
4
  Summary: Python client for Redis database and key-value store
5
5
  Home-page: https://github.com/redis/redis-py
6
6
  Author: Redis Inc.
@@ -375,7 +375,7 @@ class Redis(
375
375
  if self.single_connection_client:
376
376
  async with self._single_conn_lock:
377
377
  if self.connection is None:
378
- self.connection = await self.connection_pool.get_connection("_")
378
+ self.connection = await self.connection_pool.get_connection()
379
379
 
380
380
  self._event_dispatcher.dispatch(
381
381
  AfterSingleConnectionInstantiationEvent(
@@ -638,7 +638,7 @@ class Redis(
638
638
  await self.initialize()
639
639
  pool = self.connection_pool
640
640
  command_name = args[0]
641
- conn = self.connection or await pool.get_connection(command_name, **options)
641
+ conn = self.connection or await pool.get_connection()
642
642
 
643
643
  if self.single_connection_client:
644
644
  await self._single_conn_lock.acquire()
@@ -712,7 +712,7 @@ class Monitor:
712
712
 
713
713
  async def connect(self):
714
714
  if self.connection is None:
715
- self.connection = await self.connection_pool.get_connection("MONITOR")
715
+ self.connection = await self.connection_pool.get_connection()
716
716
 
717
717
  async def __aenter__(self):
718
718
  await self.connect()
@@ -900,9 +900,7 @@ class PubSub:
900
900
  Ensure that the PubSub is connected
901
901
  """
902
902
  if self.connection is None:
903
- self.connection = await self.connection_pool.get_connection(
904
- "pubsub", self.shard_hint
905
- )
903
+ self.connection = await self.connection_pool.get_connection()
906
904
  # register a callback that re-subscribes to any channels we
907
905
  # were listening to when we were disconnected
908
906
  self.connection.register_connect_callback(self.on_connect)
@@ -1370,9 +1368,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
1370
1368
  conn = self.connection
1371
1369
  # if this is the first call, we need a connection
1372
1370
  if not conn:
1373
- conn = await self.connection_pool.get_connection(
1374
- command_name, self.shard_hint
1375
- )
1371
+ conn = await self.connection_pool.get_connection()
1376
1372
  self.connection = conn
1377
1373
 
1378
1374
  return await conn.retry.call_with_retry(
@@ -1554,7 +1550,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
1554
1550
  await self.reset()
1555
1551
  raise
1556
1552
 
1557
- async def execute(self, raise_on_error: bool = True):
1553
+ async def execute(self, raise_on_error: bool = True) -> List[Any]:
1558
1554
  """Execute all the commands in the current pipeline"""
1559
1555
  stack = self.command_stack
1560
1556
  if not stack and not self.watching:
@@ -1568,7 +1564,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
1568
1564
 
1569
1565
  conn = self.connection
1570
1566
  if not conn:
1571
- conn = await self.connection_pool.get_connection("MULTI", self.shard_hint)
1567
+ conn = await self.connection_pool.get_connection()
1572
1568
  # assign to self.connection so reset() releases the connection
1573
1569
  # back to the pool after we're done
1574
1570
  self.connection = conn
@@ -39,6 +39,7 @@ from redis.cluster import (
39
39
  SLOT_ID,
40
40
  AbstractRedisCluster,
41
41
  LoadBalancer,
42
+ LoadBalancingStrategy,
42
43
  block_pipeline_command,
43
44
  get_node_name,
44
45
  parse_cluster_slots,
@@ -67,6 +68,7 @@ from redis.exceptions import (
67
68
  )
68
69
  from redis.typing import AnyKeyT, EncodableT, KeyT
69
70
  from redis.utils import (
71
+ deprecated_args,
70
72
  deprecated_function,
71
73
  dict_merge,
72
74
  get_lib_version,
@@ -133,9 +135,17 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
133
135
  | See:
134
136
  https://redis.io/docs/manual/scaling/#redis-cluster-configuration-parameters
135
137
  :param read_from_replicas:
136
- | Enable read from replicas in READONLY mode. You can read possibly stale data.
138
+ | @deprecated - please use load_balancing_strategy instead
139
+ | Enable read from replicas in READONLY mode.
137
140
  When set to true, read commands will be assigned between the primary and
138
141
  its replications in a Round-Robin manner.
142
+ The data read from replicas is eventually consistent
143
+ with the data in primary nodes.
144
+ :param load_balancing_strategy:
145
+ | Enable read from replicas in READONLY mode and defines the load balancing
146
+ strategy that will be used for cluster node selection.
147
+ The data read from replicas is eventually consistent
148
+ with the data in primary nodes.
139
149
  :param reinitialize_steps:
140
150
  | Specifies the number of MOVED errors that need to occur before reinitializing
141
151
  the whole cluster topology. If a MOVED error occurs and the cluster does not
@@ -228,6 +238,11 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
228
238
  "result_callbacks",
229
239
  )
230
240
 
241
+ @deprecated_args(
242
+ args_to_warn=["read_from_replicas"],
243
+ reason="Please configure the 'load_balancing_strategy' instead",
244
+ version="5.3.0",
245
+ )
231
246
  def __init__(
232
247
  self,
233
248
  host: Optional[str] = None,
@@ -236,6 +251,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
236
251
  startup_nodes: Optional[List["ClusterNode"]] = None,
237
252
  require_full_coverage: bool = True,
238
253
  read_from_replicas: bool = False,
254
+ load_balancing_strategy: Optional[LoadBalancingStrategy] = None,
239
255
  reinitialize_steps: int = 5,
240
256
  cluster_error_retry_attempts: int = 3,
241
257
  connection_error_retry_attempts: int = 3,
@@ -335,7 +351,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
335
351
  }
336
352
  )
337
353
 
338
- if read_from_replicas:
354
+ if read_from_replicas or load_balancing_strategy:
339
355
  # Call our on_connect function to configure READONLY mode
340
356
  kwargs["redis_connect_func"] = self.on_connect
341
357
 
@@ -384,6 +400,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
384
400
  )
385
401
  self.encoder = Encoder(encoding, encoding_errors, decode_responses)
386
402
  self.read_from_replicas = read_from_replicas
403
+ self.load_balancing_strategy = load_balancing_strategy
387
404
  self.reinitialize_steps = reinitialize_steps
388
405
  self.cluster_error_retry_attempts = cluster_error_retry_attempts
389
406
  self.connection_error_retry_attempts = connection_error_retry_attempts
@@ -602,6 +619,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
602
619
  self.nodes_manager.get_node_from_slot(
603
620
  await self._determine_slot(command, *args),
604
621
  self.read_from_replicas and command in READ_COMMANDS,
622
+ self.load_balancing_strategy if command in READ_COMMANDS else None,
605
623
  )
606
624
  ]
607
625
 
@@ -782,7 +800,13 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
782
800
  # refresh the target node
783
801
  slot = await self._determine_slot(*args)
784
802
  target_node = self.nodes_manager.get_node_from_slot(
785
- slot, self.read_from_replicas and args[0] in READ_COMMANDS
803
+ slot,
804
+ self.read_from_replicas and args[0] in READ_COMMANDS,
805
+ (
806
+ self.load_balancing_strategy
807
+ if args[0] in READ_COMMANDS
808
+ else None
809
+ ),
786
810
  )
787
811
  moved = False
788
812
 
@@ -799,10 +823,16 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
799
823
  # and try again with the new setup
800
824
  await self.aclose()
801
825
  raise
802
- except ClusterDownError:
826
+ except (ClusterDownError, SlotNotCoveredError):
803
827
  # ClusterDownError can occur during a failover and to get
804
828
  # self-healed, we will try to reinitialize the cluster layout
805
829
  # and retry executing the command
830
+
831
+ # SlotNotCoveredError can occur when the cluster is not fully
832
+ # initialized or can be temporary issue.
833
+ # We will try to reinitialize the cluster topology
834
+ # and retry executing the command
835
+
806
836
  await self.aclose()
807
837
  await asyncio.sleep(0.25)
808
838
  raise
@@ -1177,9 +1207,7 @@ class NodesManager:
1177
1207
  return self.nodes_cache.get(node_name)
1178
1208
  else:
1179
1209
  raise DataError(
1180
- "get_node requires one of the following: "
1181
- "1. node name "
1182
- "2. host and port"
1210
+ "get_node requires one of the following: 1. node name 2. host and port"
1183
1211
  )
1184
1212
 
1185
1213
  def set_nodes(
@@ -1239,17 +1267,24 @@ class NodesManager:
1239
1267
  self._moved_exception = None
1240
1268
 
1241
1269
  def get_node_from_slot(
1242
- self, slot: int, read_from_replicas: bool = False
1270
+ self,
1271
+ slot: int,
1272
+ read_from_replicas: bool = False,
1273
+ load_balancing_strategy=None,
1243
1274
  ) -> "ClusterNode":
1244
1275
  if self._moved_exception:
1245
1276
  self._update_moved_slots()
1246
1277
 
1278
+ if read_from_replicas is True and load_balancing_strategy is None:
1279
+ load_balancing_strategy = LoadBalancingStrategy.ROUND_ROBIN
1280
+
1247
1281
  try:
1248
- if read_from_replicas:
1249
- # get the server index in a Round-Robin manner
1282
+ if len(self.slots_cache[slot]) > 1 and load_balancing_strategy:
1283
+ # get the server index using the strategy defined
1284
+ # in load_balancing_strategy
1250
1285
  primary_name = self.slots_cache[slot][0].name
1251
1286
  node_idx = self.read_load_balancer.get_server_index(
1252
- primary_name, len(self.slots_cache[slot])
1287
+ primary_name, len(self.slots_cache[slot]), load_balancing_strategy
1253
1288
  )
1254
1289
  return self.slots_cache[slot][node_idx]
1255
1290
  return self.slots_cache[slot][0]
@@ -1361,7 +1396,7 @@ class NodesManager:
1361
1396
  if len(disagreements) > 5:
1362
1397
  raise RedisClusterException(
1363
1398
  f"startup_nodes could not agree on a valid "
1364
- f'slots cache: {", ".join(disagreements)}'
1399
+ f"slots cache: {', '.join(disagreements)}"
1365
1400
  )
1366
1401
 
1367
1402
  # Validate if all slots are covered or if we should try next startup node
@@ -29,7 +29,7 @@ from urllib.parse import ParseResult, parse_qs, unquote, urlparse
29
29
 
30
30
  from ..auth.token import TokenInterface
31
31
  from ..event import AsyncAfterConnectionReleasedEvent, EventDispatcher
32
- from ..utils import format_error_message
32
+ from ..utils import deprecated_args, format_error_message
33
33
 
34
34
  # the functionality is available in 3.11.x but has a major issue before
35
35
  # 3.11.3. See https://github.com/redis/redis-py/issues/2633
@@ -1087,7 +1087,12 @@ class ConnectionPool:
1087
1087
  or len(self._in_use_connections) < self.max_connections
1088
1088
  )
1089
1089
 
1090
- async def get_connection(self, command_name, *keys, **options):
1090
+ @deprecated_args(
1091
+ args_to_warn=["*"],
1092
+ reason="Use get_connection() without args instead",
1093
+ version="5.3.0",
1094
+ )
1095
+ async def get_connection(self, command_name=None, *keys, **options):
1091
1096
  async with self._lock:
1092
1097
  """Get a connected connection from the pool"""
1093
1098
  connection = self.get_available_connection()
@@ -1255,7 +1260,12 @@ class BlockingConnectionPool(ConnectionPool):
1255
1260
  self._condition = asyncio.Condition()
1256
1261
  self.timeout = timeout
1257
1262
 
1258
- async def get_connection(self, command_name, *keys, **options):
1263
+ @deprecated_args(
1264
+ args_to_warn=["*"],
1265
+ reason="Use get_connection() without args instead",
1266
+ version="5.3.0",
1267
+ )
1268
+ async def get_connection(self, command_name=None, *keys, **options):
1259
1269
  """Gets a connection from the pool, blocking until one is available"""
1260
1270
  try:
1261
1271
  async with self._condition:
@@ -110,5 +110,20 @@ class DecorrelatedJitterBackoff(AbstractBackoff):
110
110
  return self._previous_backoff
111
111
 
112
112
 
113
+ class ExponentialWithJitterBackoff(AbstractBackoff):
114
+ """Exponential backoff upon failure, with jitter"""
115
+
116
+ def __init__(self, cap: float = DEFAULT_CAP, base: float = DEFAULT_BASE) -> None:
117
+ """
118
+ `cap`: maximum backoff time in seconds
119
+ `base`: base backoff time in seconds
120
+ """
121
+ self._cap = cap
122
+ self._base = base
123
+
124
+ def compute(self, failures: int) -> float:
125
+ return min(self._cap, random.random() * self._base * 2**failures)
126
+
127
+
113
128
  def default_backoff():
114
129
  return EqualJitterBackoff()
@@ -4,7 +4,17 @@ import threading
4
4
  import time
5
5
  import warnings
6
6
  from itertools import chain
7
- from typing import Any, Callable, Dict, List, Optional, Type, Union
7
+ from typing import (
8
+ TYPE_CHECKING,
9
+ Any,
10
+ Callable,
11
+ Dict,
12
+ List,
13
+ Mapping,
14
+ Optional,
15
+ Type,
16
+ Union,
17
+ )
8
18
 
9
19
  from redis._parsers.encoders import Encoder
10
20
  from redis._parsers.helpers import (
@@ -53,6 +63,11 @@ from redis.utils import (
53
63
  str_if_bytes,
54
64
  )
55
65
 
66
+ if TYPE_CHECKING:
67
+ import ssl
68
+
69
+ import OpenSSL
70
+
56
71
  SYM_EMPTY = b""
57
72
  EMPTY_RESPONSE = "EMPTY_RESPONSE"
58
73
 
@@ -175,47 +190,47 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
175
190
 
176
191
  def __init__(
177
192
  self,
178
- host="localhost",
179
- port=6379,
180
- db=0,
181
- password=None,
182
- socket_timeout=None,
183
- socket_connect_timeout=None,
184
- socket_keepalive=None,
185
- socket_keepalive_options=None,
186
- connection_pool=None,
187
- unix_socket_path=None,
188
- encoding="utf-8",
189
- encoding_errors="strict",
190
- charset=None,
191
- errors=None,
192
- decode_responses=False,
193
- retry_on_timeout=False,
194
- retry_on_error=None,
195
- ssl=False,
196
- ssl_keyfile=None,
197
- ssl_certfile=None,
198
- ssl_cert_reqs="required",
199
- ssl_ca_certs=None,
200
- ssl_ca_path=None,
201
- ssl_ca_data=None,
202
- ssl_check_hostname=False,
203
- ssl_password=None,
204
- ssl_validate_ocsp=False,
205
- ssl_validate_ocsp_stapled=False,
206
- ssl_ocsp_context=None,
207
- ssl_ocsp_expected_cert=None,
208
- ssl_min_version=None,
209
- ssl_ciphers=None,
210
- max_connections=None,
211
- single_connection_client=False,
212
- health_check_interval=0,
213
- client_name=None,
214
- lib_name="redis-py",
215
- lib_version=get_lib_version(),
216
- username=None,
217
- retry=None,
218
- redis_connect_func=None,
193
+ host: str = "localhost",
194
+ port: int = 6379,
195
+ db: int = 0,
196
+ password: Optional[str] = None,
197
+ socket_timeout: Optional[float] = None,
198
+ socket_connect_timeout: Optional[float] = None,
199
+ socket_keepalive: Optional[bool] = None,
200
+ socket_keepalive_options: Optional[Mapping[int, Union[int, bytes]]] = None,
201
+ connection_pool: Optional[ConnectionPool] = None,
202
+ unix_socket_path: Optional[str] = None,
203
+ encoding: str = "utf-8",
204
+ encoding_errors: str = "strict",
205
+ charset: Optional[str] = None,
206
+ errors: Optional[str] = None,
207
+ decode_responses: bool = False,
208
+ retry_on_timeout: bool = False,
209
+ retry_on_error: Optional[List[Type[Exception]]] = None,
210
+ ssl: bool = False,
211
+ ssl_keyfile: Optional[str] = None,
212
+ ssl_certfile: Optional[str] = None,
213
+ ssl_cert_reqs: str = "required",
214
+ ssl_ca_certs: Optional[str] = None,
215
+ ssl_ca_path: Optional[str] = None,
216
+ ssl_ca_data: Optional[str] = None,
217
+ ssl_check_hostname: bool = False,
218
+ ssl_password: Optional[str] = None,
219
+ ssl_validate_ocsp: bool = False,
220
+ ssl_validate_ocsp_stapled: bool = False,
221
+ ssl_ocsp_context: Optional["OpenSSL.SSL.Context"] = None,
222
+ ssl_ocsp_expected_cert: Optional[str] = None,
223
+ ssl_min_version: Optional["ssl.TLSVersion"] = None,
224
+ ssl_ciphers: Optional[str] = None,
225
+ max_connections: Optional[int] = None,
226
+ single_connection_client: bool = False,
227
+ health_check_interval: int = 0,
228
+ client_name: Optional[str] = None,
229
+ lib_name: Optional[str] = "redis-py",
230
+ lib_version: Optional[str] = get_lib_version(),
231
+ username: Optional[str] = None,
232
+ retry: Optional[Retry] = None,
233
+ redis_connect_func: Optional[Callable[[], None]] = None,
219
234
  credential_provider: Optional[CredentialProvider] = None,
220
235
  protocol: Optional[int] = 2,
221
236
  cache: Optional[CacheInterface] = None,
@@ -351,7 +366,7 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
351
366
  self.connection = None
352
367
  self._single_connection_client = single_connection_client
353
368
  if self._single_connection_client:
354
- self.connection = self.connection_pool.get_connection("_")
369
+ self.connection = self.connection_pool.get_connection()
355
370
  self._event_dispatcher.dispatch(
356
371
  AfterSingleConnectionInstantiationEvent(
357
372
  self.connection, ClientType.SYNC, self.single_connection_lock
@@ -550,7 +565,7 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
550
565
  def __del__(self):
551
566
  self.close()
552
567
 
553
- def close(self):
568
+ def close(self) -> None:
554
569
  # In case a connection property does not yet exist
555
570
  # (due to a crash earlier in the Redis() constructor), return
556
571
  # immediately as there is nothing to clean-up.
@@ -593,7 +608,7 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
593
608
  """Execute a command and return a parsed response"""
594
609
  pool = self.connection_pool
595
610
  command_name = args[0]
596
- conn = self.connection or pool.get_connection(command_name, **options)
611
+ conn = self.connection or pool.get_connection()
597
612
 
598
613
  if self._single_connection_client:
599
614
  self.single_connection_lock.acquire()
@@ -652,7 +667,7 @@ class Monitor:
652
667
 
653
668
  def __init__(self, connection_pool):
654
669
  self.connection_pool = connection_pool
655
- self.connection = self.connection_pool.get_connection("MONITOR")
670
+ self.connection = self.connection_pool.get_connection()
656
671
 
657
672
  def __enter__(self):
658
673
  self.connection.send_command("MONITOR")
@@ -825,9 +840,7 @@ class PubSub:
825
840
  # subscribed to one or more channels
826
841
 
827
842
  if self.connection is None:
828
- self.connection = self.connection_pool.get_connection(
829
- "pubsub", self.shard_hint
830
- )
843
+ self.connection = self.connection_pool.get_connection()
831
844
  # register a callback that re-subscribes to any channels we
832
845
  # were listening to when we were disconnected
833
846
  self.connection.register_connect_callback(self.on_connect)
@@ -1382,7 +1395,7 @@ class Pipeline(Redis):
1382
1395
  conn = self.connection
1383
1396
  # if this is the first call, we need a connection
1384
1397
  if not conn:
1385
- conn = self.connection_pool.get_connection(command_name, self.shard_hint)
1398
+ conn = self.connection_pool.get_connection()
1386
1399
  self.connection = conn
1387
1400
 
1388
1401
  return conn.retry.call_with_retry(
@@ -1551,11 +1564,10 @@ class Pipeline(Redis):
1551
1564
  conn.retry_on_error is None
1552
1565
  or isinstance(error, tuple(conn.retry_on_error)) is False
1553
1566
  ):
1554
-
1555
1567
  self.reset()
1556
1568
  raise error
1557
1569
 
1558
- def execute(self, raise_on_error=True):
1570
+ def execute(self, raise_on_error: bool = True) -> List[Any]:
1559
1571
  """Execute all the commands in the current pipeline"""
1560
1572
  stack = self.command_stack
1561
1573
  if not stack and not self.watching:
@@ -1569,7 +1581,7 @@ class Pipeline(Redis):
1569
1581
 
1570
1582
  conn = self.connection
1571
1583
  if not conn:
1572
- conn = self.connection_pool.get_connection("MULTI", self.shard_hint)
1584
+ conn = self.connection_pool.get_connection()
1573
1585
  # assign to self.connection so reset() releases the connection
1574
1586
  # back to the pool after we're done
1575
1587
  self.connection = conn