redis 6.0.0b2__py3-none-any.whl → 6.2.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.
- redis/__init__.py +8 -1
- redis/_parsers/__init__.py +8 -1
- redis/_parsers/base.py +53 -1
- redis/_parsers/hiredis.py +72 -5
- redis/_parsers/resp3.py +12 -37
- redis/asyncio/client.py +76 -70
- redis/asyncio/cluster.py +796 -104
- redis/asyncio/connection.py +8 -10
- redis/asyncio/retry.py +12 -0
- redis/backoff.py +54 -0
- redis/client.py +101 -89
- redis/cluster.py +1088 -365
- redis/commands/core.py +104 -104
- redis/commands/helpers.py +19 -6
- redis/commands/json/__init__.py +1 -1
- redis/commands/json/commands.py +8 -8
- redis/commands/redismodules.py +20 -10
- redis/commands/search/commands.py +2 -2
- redis/commands/timeseries/__init__.py +1 -1
- redis/connection.py +19 -9
- redis/exceptions.py +18 -0
- redis/retry.py +25 -0
- redis/typing.py +0 -4
- redis/utils.py +5 -2
- {redis-6.0.0b2.dist-info → redis-6.2.0.dist-info}/METADATA +16 -12
- {redis-6.0.0b2.dist-info → redis-6.2.0.dist-info}/RECORD +28 -28
- {redis-6.0.0b2.dist-info → redis-6.2.0.dist-info}/WHEEL +0 -0
- {redis-6.0.0b2.dist-info → redis-6.2.0.dist-info}/licenses/LICENSE +0 -0
redis/asyncio/client.py
CHANGED
|
@@ -39,6 +39,7 @@ from redis.asyncio.connection import (
|
|
|
39
39
|
)
|
|
40
40
|
from redis.asyncio.lock import Lock
|
|
41
41
|
from redis.asyncio.retry import Retry
|
|
42
|
+
from redis.backoff import ExponentialWithJitterBackoff
|
|
42
43
|
from redis.client import (
|
|
43
44
|
EMPTY_RESPONSE,
|
|
44
45
|
NEVER_DECODE,
|
|
@@ -65,14 +66,13 @@ from redis.exceptions import (
|
|
|
65
66
|
PubSubError,
|
|
66
67
|
RedisError,
|
|
67
68
|
ResponseError,
|
|
68
|
-
TimeoutError,
|
|
69
69
|
WatchError,
|
|
70
70
|
)
|
|
71
71
|
from redis.typing import ChannelT, EncodableT, KeyT
|
|
72
72
|
from redis.utils import (
|
|
73
|
-
HIREDIS_AVAILABLE,
|
|
74
73
|
SSL_AVAILABLE,
|
|
75
74
|
_set_info_logger,
|
|
75
|
+
deprecated_args,
|
|
76
76
|
deprecated_function,
|
|
77
77
|
get_lib_version,
|
|
78
78
|
safe_str,
|
|
@@ -208,6 +208,11 @@ class Redis(
|
|
|
208
208
|
client.auto_close_connection_pool = True
|
|
209
209
|
return client
|
|
210
210
|
|
|
211
|
+
@deprecated_args(
|
|
212
|
+
args_to_warn=["retry_on_timeout"],
|
|
213
|
+
reason="TimeoutError is included by default.",
|
|
214
|
+
version="6.0.0",
|
|
215
|
+
)
|
|
211
216
|
def __init__(
|
|
212
217
|
self,
|
|
213
218
|
*,
|
|
@@ -225,6 +230,9 @@ class Redis(
|
|
|
225
230
|
encoding_errors: str = "strict",
|
|
226
231
|
decode_responses: bool = False,
|
|
227
232
|
retry_on_timeout: bool = False,
|
|
233
|
+
retry: Retry = Retry(
|
|
234
|
+
backoff=ExponentialWithJitterBackoff(base=1, cap=10), retries=3
|
|
235
|
+
),
|
|
228
236
|
retry_on_error: Optional[list] = None,
|
|
229
237
|
ssl: bool = False,
|
|
230
238
|
ssl_keyfile: Optional[str] = None,
|
|
@@ -232,7 +240,7 @@ class Redis(
|
|
|
232
240
|
ssl_cert_reqs: Union[str, VerifyMode] = "required",
|
|
233
241
|
ssl_ca_certs: Optional[str] = None,
|
|
234
242
|
ssl_ca_data: Optional[str] = None,
|
|
235
|
-
ssl_check_hostname: bool =
|
|
243
|
+
ssl_check_hostname: bool = True,
|
|
236
244
|
ssl_min_version: Optional[TLSVersion] = None,
|
|
237
245
|
ssl_ciphers: Optional[str] = None,
|
|
238
246
|
max_connections: Optional[int] = None,
|
|
@@ -242,7 +250,6 @@ class Redis(
|
|
|
242
250
|
lib_name: Optional[str] = "redis-py",
|
|
243
251
|
lib_version: Optional[str] = get_lib_version(),
|
|
244
252
|
username: Optional[str] = None,
|
|
245
|
-
retry: Optional[Retry] = None,
|
|
246
253
|
auto_close_connection_pool: Optional[bool] = None,
|
|
247
254
|
redis_connect_func=None,
|
|
248
255
|
credential_provider: Optional[CredentialProvider] = None,
|
|
@@ -251,10 +258,24 @@ class Redis(
|
|
|
251
258
|
):
|
|
252
259
|
"""
|
|
253
260
|
Initialize a new Redis client.
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
261
|
+
|
|
262
|
+
To specify a retry policy for specific errors, you have two options:
|
|
263
|
+
|
|
264
|
+
1. Set the `retry_on_error` to a list of the error/s to retry on, and
|
|
265
|
+
you can also set `retry` to a valid `Retry` object(in case the default
|
|
266
|
+
one is not appropriate) - with this approach the retries will be triggered
|
|
267
|
+
on the default errors specified in the Retry object enriched with the
|
|
268
|
+
errors specified in `retry_on_error`.
|
|
269
|
+
|
|
270
|
+
2. Define a `Retry` object with configured 'supported_errors' and set
|
|
271
|
+
it to the `retry` parameter - with this approach you completely redefine
|
|
272
|
+
the errors on which retries will happen.
|
|
273
|
+
|
|
274
|
+
`retry_on_timeout` is deprecated - please include the TimeoutError
|
|
275
|
+
either in the Retry object or in the `retry_on_error` list.
|
|
276
|
+
|
|
277
|
+
When 'connection_pool' is provided - the retry configuration of the
|
|
278
|
+
provided pool will be used.
|
|
258
279
|
"""
|
|
259
280
|
kwargs: Dict[str, Any]
|
|
260
281
|
if event_dispatcher is None:
|
|
@@ -280,8 +301,6 @@ class Redis(
|
|
|
280
301
|
# Create internal connection pool, expected to be closed by Redis instance
|
|
281
302
|
if not retry_on_error:
|
|
282
303
|
retry_on_error = []
|
|
283
|
-
if retry_on_timeout is True:
|
|
284
|
-
retry_on_error.append(TimeoutError)
|
|
285
304
|
kwargs = {
|
|
286
305
|
"db": db,
|
|
287
306
|
"username": username,
|
|
@@ -291,7 +310,6 @@ class Redis(
|
|
|
291
310
|
"encoding": encoding,
|
|
292
311
|
"encoding_errors": encoding_errors,
|
|
293
312
|
"decode_responses": decode_responses,
|
|
294
|
-
"retry_on_timeout": retry_on_timeout,
|
|
295
313
|
"retry_on_error": retry_on_error,
|
|
296
314
|
"retry": copy.deepcopy(retry),
|
|
297
315
|
"max_connections": max_connections,
|
|
@@ -403,10 +421,10 @@ class Redis(
|
|
|
403
421
|
"""Get the connection's key-word arguments"""
|
|
404
422
|
return self.connection_pool.connection_kwargs
|
|
405
423
|
|
|
406
|
-
def get_retry(self) -> Optional[
|
|
424
|
+
def get_retry(self) -> Optional[Retry]:
|
|
407
425
|
return self.get_connection_kwargs().get("retry")
|
|
408
426
|
|
|
409
|
-
def set_retry(self, retry:
|
|
427
|
+
def set_retry(self, retry: Retry) -> None:
|
|
410
428
|
self.get_connection_kwargs().update({"retry": retry})
|
|
411
429
|
self.connection_pool.set_retry(retry)
|
|
412
430
|
|
|
@@ -633,18 +651,17 @@ class Redis(
|
|
|
633
651
|
await conn.send_command(*args)
|
|
634
652
|
return await self.parse_response(conn, command_name, **options)
|
|
635
653
|
|
|
636
|
-
async def
|
|
654
|
+
async def _close_connection(self, conn: Connection):
|
|
637
655
|
"""
|
|
638
|
-
Close the connection
|
|
639
|
-
|
|
640
|
-
|
|
656
|
+
Close the connection before retrying.
|
|
657
|
+
|
|
658
|
+
The supported exceptions are already checked in the
|
|
659
|
+
retry object so we don't need to do it here.
|
|
660
|
+
|
|
661
|
+
After we disconnect the connection, it will try to reconnect and
|
|
662
|
+
do a health check as part of the send_command logic(on connection level).
|
|
641
663
|
"""
|
|
642
664
|
await conn.disconnect()
|
|
643
|
-
if (
|
|
644
|
-
conn.retry_on_error is None
|
|
645
|
-
or isinstance(error, tuple(conn.retry_on_error)) is False
|
|
646
|
-
):
|
|
647
|
-
raise error
|
|
648
665
|
|
|
649
666
|
# COMMAND EXECUTION AND PROTOCOL PARSING
|
|
650
667
|
async def execute_command(self, *args, **options):
|
|
@@ -661,7 +678,7 @@ class Redis(
|
|
|
661
678
|
lambda: self._send_command_parse_response(
|
|
662
679
|
conn, command_name, *args, **options
|
|
663
680
|
),
|
|
664
|
-
lambda
|
|
681
|
+
lambda _: self._close_connection(conn),
|
|
665
682
|
)
|
|
666
683
|
finally:
|
|
667
684
|
if self.single_connection_client:
|
|
@@ -920,7 +937,7 @@ class PubSub:
|
|
|
920
937
|
self.connection.register_connect_callback(self.on_connect)
|
|
921
938
|
else:
|
|
922
939
|
await self.connection.connect()
|
|
923
|
-
if self.push_handler_func is not None
|
|
940
|
+
if self.push_handler_func is not None:
|
|
924
941
|
self.connection._parser.set_pubsub_push_handler(self.push_handler_func)
|
|
925
942
|
|
|
926
943
|
self._event_dispatcher.dispatch(
|
|
@@ -929,19 +946,11 @@ class PubSub:
|
|
|
929
946
|
)
|
|
930
947
|
)
|
|
931
948
|
|
|
932
|
-
async def
|
|
949
|
+
async def _reconnect(self, conn):
|
|
933
950
|
"""
|
|
934
|
-
|
|
935
|
-
if retry_on_error is not set or the error is not one
|
|
936
|
-
of the specified error types. Otherwise, try to
|
|
937
|
-
reconnect
|
|
951
|
+
Try to reconnect
|
|
938
952
|
"""
|
|
939
953
|
await conn.disconnect()
|
|
940
|
-
if (
|
|
941
|
-
conn.retry_on_error is None
|
|
942
|
-
or isinstance(error, tuple(conn.retry_on_error)) is False
|
|
943
|
-
):
|
|
944
|
-
raise error
|
|
945
954
|
await conn.connect()
|
|
946
955
|
|
|
947
956
|
async def _execute(self, conn, command, *args, **kwargs):
|
|
@@ -954,7 +963,7 @@ class PubSub:
|
|
|
954
963
|
"""
|
|
955
964
|
return await conn.retry.call_with_retry(
|
|
956
965
|
lambda: command(*args, **kwargs),
|
|
957
|
-
lambda
|
|
966
|
+
lambda _: self._reconnect(conn),
|
|
958
967
|
)
|
|
959
968
|
|
|
960
969
|
async def parse_response(self, block: bool = True, timeout: float = 0):
|
|
@@ -1245,7 +1254,8 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
|
|
1245
1254
|
in one transmission. This is convenient for batch processing, such as
|
|
1246
1255
|
saving all the values in a list to Redis.
|
|
1247
1256
|
|
|
1248
|
-
All commands executed within a pipeline
|
|
1257
|
+
All commands executed within a pipeline(when running in transactional mode,
|
|
1258
|
+
which is the default behavior) are wrapped with MULTI and EXEC
|
|
1249
1259
|
calls. This guarantees all commands executed in the pipeline will be
|
|
1250
1260
|
executed atomically.
|
|
1251
1261
|
|
|
@@ -1274,7 +1284,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
|
|
1274
1284
|
self.shard_hint = shard_hint
|
|
1275
1285
|
self.watching = False
|
|
1276
1286
|
self.command_stack: CommandStackT = []
|
|
1277
|
-
self.scripts: Set[
|
|
1287
|
+
self.scripts: Set[Script] = set()
|
|
1278
1288
|
self.explicit_transaction = False
|
|
1279
1289
|
|
|
1280
1290
|
async def __aenter__(self: _RedisT) -> _RedisT:
|
|
@@ -1346,36 +1356,36 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
|
|
1346
1356
|
return self.immediate_execute_command(*args, **kwargs)
|
|
1347
1357
|
return self.pipeline_execute_command(*args, **kwargs)
|
|
1348
1358
|
|
|
1349
|
-
async def
|
|
1359
|
+
async def _disconnect_reset_raise_on_watching(
|
|
1360
|
+
self,
|
|
1361
|
+
conn: Connection,
|
|
1362
|
+
error: Exception,
|
|
1363
|
+
):
|
|
1350
1364
|
"""
|
|
1351
|
-
Close the connection
|
|
1352
|
-
raise an exception if we were watching
|
|
1353
|
-
|
|
1354
|
-
|
|
1365
|
+
Close the connection reset watching state and
|
|
1366
|
+
raise an exception if we were watching.
|
|
1367
|
+
|
|
1368
|
+
The supported exceptions are already checked in the
|
|
1369
|
+
retry object so we don't need to do it here.
|
|
1370
|
+
|
|
1371
|
+
After we disconnect the connection, it will try to reconnect and
|
|
1372
|
+
do a health check as part of the send_command logic(on connection level).
|
|
1355
1373
|
"""
|
|
1356
1374
|
await conn.disconnect()
|
|
1357
1375
|
# if we were already watching a variable, the watch is no longer
|
|
1358
1376
|
# valid since this connection has died. raise a WatchError, which
|
|
1359
1377
|
# indicates the user should retry this transaction.
|
|
1360
1378
|
if self.watching:
|
|
1361
|
-
await self.
|
|
1379
|
+
await self.reset()
|
|
1362
1380
|
raise WatchError(
|
|
1363
|
-
"A
|
|
1381
|
+
f"A {type(error).__name__} occurred while watching one or more keys"
|
|
1364
1382
|
)
|
|
1365
|
-
# if retry_on_error is not set or the error is not one
|
|
1366
|
-
# of the specified error types, raise it
|
|
1367
|
-
if (
|
|
1368
|
-
conn.retry_on_error is None
|
|
1369
|
-
or isinstance(error, tuple(conn.retry_on_error)) is False
|
|
1370
|
-
):
|
|
1371
|
-
await self.aclose()
|
|
1372
|
-
raise
|
|
1373
1383
|
|
|
1374
1384
|
async def immediate_execute_command(self, *args, **options):
|
|
1375
1385
|
"""
|
|
1376
|
-
Execute a command immediately, but don't auto-retry on
|
|
1377
|
-
|
|
1378
|
-
issuing WATCH or subsequent commands retrieving their values but before
|
|
1386
|
+
Execute a command immediately, but don't auto-retry on the supported
|
|
1387
|
+
errors for retry if we're already WATCHing a variable.
|
|
1388
|
+
Used when issuing WATCH or subsequent commands retrieving their values but before
|
|
1379
1389
|
MULTI is called.
|
|
1380
1390
|
"""
|
|
1381
1391
|
command_name = args[0]
|
|
@@ -1389,7 +1399,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
|
|
1389
1399
|
lambda: self._send_command_parse_response(
|
|
1390
1400
|
conn, command_name, *args, **options
|
|
1391
1401
|
),
|
|
1392
|
-
lambda error: self.
|
|
1402
|
+
lambda error: self._disconnect_reset_raise_on_watching(conn, error),
|
|
1393
1403
|
)
|
|
1394
1404
|
|
|
1395
1405
|
def pipeline_execute_command(self, *args, **options):
|
|
@@ -1544,11 +1554,15 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
|
|
1544
1554
|
if not exist:
|
|
1545
1555
|
s.sha = await immediate("SCRIPT LOAD", s.script)
|
|
1546
1556
|
|
|
1547
|
-
async def
|
|
1557
|
+
async def _disconnect_raise_on_watching(self, conn: Connection, error: Exception):
|
|
1548
1558
|
"""
|
|
1549
|
-
Close the connection, raise an exception if we were watching
|
|
1550
|
-
|
|
1551
|
-
|
|
1559
|
+
Close the connection, raise an exception if we were watching.
|
|
1560
|
+
|
|
1561
|
+
The supported exceptions are already checked in the
|
|
1562
|
+
retry object so we don't need to do it here.
|
|
1563
|
+
|
|
1564
|
+
After we disconnect the connection, it will try to reconnect and
|
|
1565
|
+
do a health check as part of the send_command logic(on connection level).
|
|
1552
1566
|
"""
|
|
1553
1567
|
await conn.disconnect()
|
|
1554
1568
|
# if we were watching a variable, the watch is no longer valid
|
|
@@ -1556,16 +1570,8 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
|
|
1556
1570
|
# indicates the user should retry this transaction.
|
|
1557
1571
|
if self.watching:
|
|
1558
1572
|
raise WatchError(
|
|
1559
|
-
"A
|
|
1573
|
+
f"A {type(error).__name__} occurred while watching one or more keys"
|
|
1560
1574
|
)
|
|
1561
|
-
# if retry_on_error is not set or the error is not one
|
|
1562
|
-
# of the specified error types, raise it
|
|
1563
|
-
if (
|
|
1564
|
-
conn.retry_on_error is None
|
|
1565
|
-
or isinstance(error, tuple(conn.retry_on_error)) is False
|
|
1566
|
-
):
|
|
1567
|
-
await self.reset()
|
|
1568
|
-
raise
|
|
1569
1575
|
|
|
1570
1576
|
async def execute(self, raise_on_error: bool = True) -> List[Any]:
|
|
1571
1577
|
"""Execute all the commands in the current pipeline"""
|
|
@@ -1590,7 +1596,7 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
|
|
1590
1596
|
try:
|
|
1591
1597
|
return await conn.retry.call_with_retry(
|
|
1592
1598
|
lambda: execute(conn, stack, raise_on_error),
|
|
1593
|
-
lambda error: self.
|
|
1599
|
+
lambda error: self._disconnect_raise_on_watching(conn, error),
|
|
1594
1600
|
)
|
|
1595
1601
|
finally:
|
|
1596
1602
|
await self.reset()
|