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.
- {redis-5.3.0b4/redis.egg-info → redis-5.3.1}/PKG-INFO +1 -1
- {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/client.py +7 -11
- {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/cluster.py +47 -12
- {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/connection.py +13 -3
- {redis-5.3.0b4 → redis-5.3.1}/redis/backoff.py +15 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/client.py +65 -53
- {redis-5.3.0b4 → redis-5.3.1}/redis/cluster.py +118 -33
- {redis-5.3.0b4 → redis-5.3.1}/redis/connection.py +30 -11
- {redis-5.3.0b4 → redis-5.3.1}/redis/typing.py +1 -1
- {redis-5.3.0b4 → redis-5.3.1}/redis/utils.py +65 -0
- {redis-5.3.0b4 → redis-5.3.1/redis.egg-info}/PKG-INFO +1 -1
- {redis-5.3.0b4 → redis-5.3.1}/redis.egg-info/SOURCES.txt +1 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis.egg-info/requires.txt +1 -1
- {redis-5.3.0b4 → redis-5.3.1}/setup.py +2 -2
- {redis-5.3.0b4 → redis-5.3.1}/tests/conftest.py +62 -37
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/conftest.py +61 -36
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_cluster.py +137 -14
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_connection.py +15 -17
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_connection_pool.py +32 -32
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_credentials.py +1 -1
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_encoding.py +1 -1
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_retry.py +2 -2
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_sentinel.py +1 -1
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_auth/test_token_manager.py +15 -21
- redis-5.3.1/tests/test_backoff.py +17 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_cache.py +3 -3
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_cluster.py +157 -18
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_commands.py +1 -1
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_connection.py +10 -15
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_connection_pool.py +68 -33
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_credentials.py +1 -1
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_multiprocessing.py +43 -5
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_retry.py +2 -2
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_sentinel.py +1 -1
- {redis-5.3.0b4 → redis-5.3.1}/INSTALL +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/LICENSE +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/MANIFEST.in +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/README.md +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/base.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/commands.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/encoders.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/helpers.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/hiredis.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/resp2.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/resp3.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/_parsers/socket.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/lock.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/retry.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/sentinel.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/asyncio/utils.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/auth/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/auth/err.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/auth/idp.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/auth/token.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/auth/token_manager.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/cache.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/bf/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/bf/commands.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/bf/info.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/cluster.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/core.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/commands.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/edge.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/exceptions.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/execution_plan.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/node.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/path.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/graph/query_result.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/helpers.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/json/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/json/_util.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/json/commands.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/json/decoders.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/json/path.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/redismodules.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/_util.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/aggregation.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/commands.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/document.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/field.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/indexDefinition.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/query.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/querystring.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/reducers.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/result.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/search/suggestion.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/sentinel.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/timeseries/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/timeseries/commands.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/timeseries/info.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/commands/timeseries/utils.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/crc.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/credentials.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/event.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/exceptions.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/lock.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/ocsp.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/retry.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis/sentinel.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis.egg-info/dependency_links.txt +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/redis.egg-info/top_level.txt +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/setup.cfg +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/mocks.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/ssl_utils.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/compat.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/mocks.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_bloom.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_commands.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_connect.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_cwe_404.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_graph.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_hash.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_json.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_lock.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_monitor.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_pipeline.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_pubsub.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_scripting.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_search.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_sentinel_managed_connection.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/test_timeseries.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/testdata/jsontestdata.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/testdata/titles.csv +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_asyncio/testdata/will_play_text.csv.bz2 +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_auth/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_auth/test_token.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_bloom.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_command_parser.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_connect.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_encoding.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_function.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_graph.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_graph_utils/__init__.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_graph_utils/test_edge.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_graph_utils/test_node.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_graph_utils/test_path.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_hash.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_helpers.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_json.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_lock.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_monitor.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_parsers/test_helpers.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_pipeline.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_pubsub.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_scripting.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_search.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_ssl.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_timeseries.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/test_utils.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/testdata/jsontestdata.py +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/testdata/titles.csv +0 -0
- {redis-5.3.0b4 → redis-5.3.1}/tests/testdata/will_play_text.csv.bz2 +0 -0
|
@@ -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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
|
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,
|
|
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,
|
|
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
|
|
1249
|
-
# get the server index
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|