redis 5.3.0b5__py3-none-any.whl → 6.0.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- redis/__init__.py +2 -11
- redis/_parsers/base.py +14 -2
- redis/asyncio/client.py +20 -12
- redis/asyncio/cluster.py +79 -56
- redis/asyncio/connection.py +40 -11
- redis/asyncio/lock.py +26 -5
- redis/asyncio/sentinel.py +9 -1
- redis/asyncio/utils.py +1 -1
- redis/auth/token.py +6 -2
- redis/backoff.py +15 -0
- redis/client.py +21 -14
- redis/cluster.py +111 -49
- redis/commands/cluster.py +1 -11
- redis/commands/core.py +218 -206
- redis/commands/helpers.py +0 -70
- redis/commands/redismodules.py +0 -20
- redis/commands/search/aggregation.py +3 -1
- redis/commands/search/commands.py +41 -14
- redis/commands/search/dialect.py +3 -0
- redis/commands/search/profile_information.py +14 -0
- redis/commands/search/query.py +5 -1
- redis/connection.py +37 -19
- redis/exceptions.py +4 -1
- redis/lock.py +24 -4
- redis/ocsp.py +2 -1
- redis/sentinel.py +1 -1
- redis/utils.py +107 -1
- {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info}/METADATA +57 -23
- {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info}/RECORD +32 -39
- {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info}/WHEEL +1 -2
- redis/commands/graph/__init__.py +0 -263
- redis/commands/graph/commands.py +0 -313
- redis/commands/graph/edge.py +0 -91
- redis/commands/graph/exceptions.py +0 -3
- redis/commands/graph/execution_plan.py +0 -211
- redis/commands/graph/node.py +0 -88
- redis/commands/graph/path.py +0 -78
- redis/commands/graph/query_result.py +0 -588
- redis-5.3.0b5.dist-info/top_level.txt +0 -1
- /redis/commands/search/{indexDefinition.py → index_definition.py} +0 -0
- {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info/licenses}/LICENSE +0 -0
redis/asyncio/lock.py
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import logging
|
|
2
3
|
import threading
|
|
3
4
|
import uuid
|
|
4
5
|
from types import SimpleNamespace
|
|
5
6
|
from typing import TYPE_CHECKING, Awaitable, Optional, Union
|
|
6
7
|
|
|
7
8
|
from redis.exceptions import LockError, LockNotOwnedError
|
|
9
|
+
from redis.typing import Number
|
|
8
10
|
|
|
9
11
|
if TYPE_CHECKING:
|
|
10
12
|
from redis.asyncio import Redis, RedisCluster
|
|
11
13
|
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
12
16
|
|
|
13
17
|
class Lock:
|
|
14
18
|
"""
|
|
@@ -82,8 +86,9 @@ class Lock:
|
|
|
82
86
|
timeout: Optional[float] = None,
|
|
83
87
|
sleep: float = 0.1,
|
|
84
88
|
blocking: bool = True,
|
|
85
|
-
blocking_timeout: Optional[
|
|
89
|
+
blocking_timeout: Optional[Number] = None,
|
|
86
90
|
thread_local: bool = True,
|
|
91
|
+
raise_on_release_error: bool = True,
|
|
87
92
|
):
|
|
88
93
|
"""
|
|
89
94
|
Create a new Lock instance named ``name`` using the Redis client
|
|
@@ -127,6 +132,11 @@ class Lock:
|
|
|
127
132
|
thread-1 would see the token value as "xyz" and would be
|
|
128
133
|
able to successfully release the thread-2's lock.
|
|
129
134
|
|
|
135
|
+
``raise_on_release_error`` indicates whether to raise an exception when
|
|
136
|
+
the lock is no longer owned when exiting the context manager. By default,
|
|
137
|
+
this is True, meaning an exception will be raised. If False, the warning
|
|
138
|
+
will be logged and the exception will be suppressed.
|
|
139
|
+
|
|
130
140
|
In some use cases it's necessary to disable thread local storage. For
|
|
131
141
|
example, if you have code where one thread acquires a lock and passes
|
|
132
142
|
that lock instance to a worker thread to release later. If thread
|
|
@@ -143,6 +153,7 @@ class Lock:
|
|
|
143
153
|
self.blocking_timeout = blocking_timeout
|
|
144
154
|
self.thread_local = bool(thread_local)
|
|
145
155
|
self.local = threading.local() if self.thread_local else SimpleNamespace()
|
|
156
|
+
self.raise_on_release_error = raise_on_release_error
|
|
146
157
|
self.local.token = None
|
|
147
158
|
self.register_scripts()
|
|
148
159
|
|
|
@@ -162,12 +173,19 @@ class Lock:
|
|
|
162
173
|
raise LockError("Unable to acquire lock within the time specified")
|
|
163
174
|
|
|
164
175
|
async def __aexit__(self, exc_type, exc_value, traceback):
|
|
165
|
-
|
|
176
|
+
try:
|
|
177
|
+
await self.release()
|
|
178
|
+
except LockError:
|
|
179
|
+
if self.raise_on_release_error:
|
|
180
|
+
raise
|
|
181
|
+
logger.warning(
|
|
182
|
+
"Lock was unlocked or no longer owned when exiting context manager."
|
|
183
|
+
)
|
|
166
184
|
|
|
167
185
|
async def acquire(
|
|
168
186
|
self,
|
|
169
187
|
blocking: Optional[bool] = None,
|
|
170
|
-
blocking_timeout: Optional[
|
|
188
|
+
blocking_timeout: Optional[Number] = None,
|
|
171
189
|
token: Optional[Union[str, bytes]] = None,
|
|
172
190
|
):
|
|
173
191
|
"""
|
|
@@ -249,7 +267,10 @@ class Lock:
|
|
|
249
267
|
"""Releases the already acquired lock"""
|
|
250
268
|
expected_token = self.local.token
|
|
251
269
|
if expected_token is None:
|
|
252
|
-
raise LockError(
|
|
270
|
+
raise LockError(
|
|
271
|
+
"Cannot release a lock that's not owned or is already unlocked.",
|
|
272
|
+
lock_name=self.name,
|
|
273
|
+
)
|
|
253
274
|
self.local.token = None
|
|
254
275
|
return self.do_release(expected_token)
|
|
255
276
|
|
|
@@ -262,7 +283,7 @@ class Lock:
|
|
|
262
283
|
raise LockNotOwnedError("Cannot release a lock that's no longer owned")
|
|
263
284
|
|
|
264
285
|
def extend(
|
|
265
|
-
self, additional_time:
|
|
286
|
+
self, additional_time: Number, replace_ttl: bool = False
|
|
266
287
|
) -> Awaitable[bool]:
|
|
267
288
|
"""
|
|
268
289
|
Adds more time to an already acquired lock.
|
redis/asyncio/sentinel.py
CHANGED
|
@@ -198,6 +198,7 @@ class Sentinel(AsyncSentinelCommands):
|
|
|
198
198
|
sentinels,
|
|
199
199
|
min_other_sentinels=0,
|
|
200
200
|
sentinel_kwargs=None,
|
|
201
|
+
force_master_ip=None,
|
|
201
202
|
**connection_kwargs,
|
|
202
203
|
):
|
|
203
204
|
# if sentinel_kwargs isn't defined, use the socket_* options from
|
|
@@ -214,6 +215,7 @@ class Sentinel(AsyncSentinelCommands):
|
|
|
214
215
|
]
|
|
215
216
|
self.min_other_sentinels = min_other_sentinels
|
|
216
217
|
self.connection_kwargs = connection_kwargs
|
|
218
|
+
self._force_master_ip = force_master_ip
|
|
217
219
|
|
|
218
220
|
async def execute_command(self, *args, **kwargs):
|
|
219
221
|
"""
|
|
@@ -277,7 +279,13 @@ class Sentinel(AsyncSentinelCommands):
|
|
|
277
279
|
sentinel,
|
|
278
280
|
self.sentinels[0],
|
|
279
281
|
)
|
|
280
|
-
|
|
282
|
+
|
|
283
|
+
ip = (
|
|
284
|
+
self._force_master_ip
|
|
285
|
+
if self._force_master_ip is not None
|
|
286
|
+
else state["ip"]
|
|
287
|
+
)
|
|
288
|
+
return ip, state["port"]
|
|
281
289
|
|
|
282
290
|
error_info = ""
|
|
283
291
|
if len(collected_errors) > 0:
|
redis/asyncio/utils.py
CHANGED
redis/auth/token.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from abc import ABC, abstractmethod
|
|
2
2
|
from datetime import datetime, timezone
|
|
3
3
|
|
|
4
|
-
import jwt
|
|
5
4
|
from redis.auth.err import InvalidTokenSchemaErr
|
|
6
5
|
|
|
7
6
|
|
|
@@ -77,10 +76,15 @@ class SimpleToken(TokenInterface):
|
|
|
77
76
|
|
|
78
77
|
|
|
79
78
|
class JWToken(TokenInterface):
|
|
80
|
-
|
|
81
79
|
REQUIRED_FIELDS = {"exp"}
|
|
82
80
|
|
|
83
81
|
def __init__(self, token: str):
|
|
82
|
+
try:
|
|
83
|
+
import jwt
|
|
84
|
+
except ImportError as ie:
|
|
85
|
+
raise ImportError(
|
|
86
|
+
f"The PyJWT library is required for {self.__class__.__name__}.",
|
|
87
|
+
) from ie
|
|
84
88
|
self._value = token
|
|
85
89
|
self._decoded = jwt.decode(
|
|
86
90
|
self._value,
|
redis/backoff.py
CHANGED
|
@@ -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()
|
redis/client.py
CHANGED
|
@@ -366,7 +366,7 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
366
366
|
self.connection = None
|
|
367
367
|
self._single_connection_client = single_connection_client
|
|
368
368
|
if self._single_connection_client:
|
|
369
|
-
self.connection = self.connection_pool.get_connection(
|
|
369
|
+
self.connection = self.connection_pool.get_connection()
|
|
370
370
|
self._event_dispatcher.dispatch(
|
|
371
371
|
AfterSingleConnectionInstantiationEvent(
|
|
372
372
|
self.connection, ClientType.SYNC, self.single_connection_lock
|
|
@@ -473,6 +473,7 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
473
473
|
blocking_timeout: Optional[float] = None,
|
|
474
474
|
lock_class: Union[None, Any] = None,
|
|
475
475
|
thread_local: bool = True,
|
|
476
|
+
raise_on_release_error: bool = True,
|
|
476
477
|
):
|
|
477
478
|
"""
|
|
478
479
|
Return a new Lock object using key ``name`` that mimics
|
|
@@ -519,6 +520,11 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
519
520
|
thread-1 would see the token value as "xyz" and would be
|
|
520
521
|
able to successfully release the thread-2's lock.
|
|
521
522
|
|
|
523
|
+
``raise_on_release_error`` indicates whether to raise an exception when
|
|
524
|
+
the lock is no longer owned when exiting the context manager. By default,
|
|
525
|
+
this is True, meaning an exception will be raised. If False, the warning
|
|
526
|
+
will be logged and the exception will be suppressed.
|
|
527
|
+
|
|
522
528
|
In some use cases it's necessary to disable thread local storage. For
|
|
523
529
|
example, if you have code where one thread acquires a lock and passes
|
|
524
530
|
that lock instance to a worker thread to release later. If thread
|
|
@@ -536,6 +542,7 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
536
542
|
blocking=blocking,
|
|
537
543
|
blocking_timeout=blocking_timeout,
|
|
538
544
|
thread_local=thread_local,
|
|
545
|
+
raise_on_release_error=raise_on_release_error,
|
|
539
546
|
)
|
|
540
547
|
|
|
541
548
|
def pubsub(self, **kwargs):
|
|
@@ -563,7 +570,10 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
563
570
|
self.close()
|
|
564
571
|
|
|
565
572
|
def __del__(self):
|
|
566
|
-
|
|
573
|
+
try:
|
|
574
|
+
self.close()
|
|
575
|
+
except Exception:
|
|
576
|
+
pass
|
|
567
577
|
|
|
568
578
|
def close(self) -> None:
|
|
569
579
|
# In case a connection property does not yet exist
|
|
@@ -608,7 +618,7 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
608
618
|
"""Execute a command and return a parsed response"""
|
|
609
619
|
pool = self.connection_pool
|
|
610
620
|
command_name = args[0]
|
|
611
|
-
conn = self.connection or pool.get_connection(
|
|
621
|
+
conn = self.connection or pool.get_connection()
|
|
612
622
|
|
|
613
623
|
if self._single_connection_client:
|
|
614
624
|
self.single_connection_lock.acquire()
|
|
@@ -667,7 +677,7 @@ class Monitor:
|
|
|
667
677
|
|
|
668
678
|
def __init__(self, connection_pool):
|
|
669
679
|
self.connection_pool = connection_pool
|
|
670
|
-
self.connection = self.connection_pool.get_connection(
|
|
680
|
+
self.connection = self.connection_pool.get_connection()
|
|
671
681
|
|
|
672
682
|
def __enter__(self):
|
|
673
683
|
self.connection.send_command("MONITOR")
|
|
@@ -840,9 +850,7 @@ class PubSub:
|
|
|
840
850
|
# subscribed to one or more channels
|
|
841
851
|
|
|
842
852
|
if self.connection is None:
|
|
843
|
-
self.connection = self.connection_pool.get_connection(
|
|
844
|
-
"pubsub", self.shard_hint
|
|
845
|
-
)
|
|
853
|
+
self.connection = self.connection_pool.get_connection()
|
|
846
854
|
# register a callback that re-subscribes to any channels we
|
|
847
855
|
# were listening to when we were disconnected
|
|
848
856
|
self.connection.register_connect_callback(self.on_connect)
|
|
@@ -952,7 +960,7 @@ class PubSub:
|
|
|
952
960
|
"did you forget to call subscribe() or psubscribe()?"
|
|
953
961
|
)
|
|
954
962
|
|
|
955
|
-
if conn.health_check_interval and time.
|
|
963
|
+
if conn.health_check_interval and time.monotonic() > conn.next_health_check:
|
|
956
964
|
conn.send_command("PING", self.HEALTH_CHECK_MESSAGE, check_health=False)
|
|
957
965
|
self.health_check_response_counter += 1
|
|
958
966
|
|
|
@@ -1102,12 +1110,12 @@ class PubSub:
|
|
|
1102
1110
|
"""
|
|
1103
1111
|
if not self.subscribed:
|
|
1104
1112
|
# Wait for subscription
|
|
1105
|
-
start_time = time.
|
|
1113
|
+
start_time = time.monotonic()
|
|
1106
1114
|
if self.subscribed_event.wait(timeout) is True:
|
|
1107
1115
|
# The connection was subscribed during the timeout time frame.
|
|
1108
1116
|
# The timeout should be adjusted based on the time spent
|
|
1109
1117
|
# waiting for the subscription
|
|
1110
|
-
time_spent = time.
|
|
1118
|
+
time_spent = time.monotonic() - start_time
|
|
1111
1119
|
timeout = max(0.0, timeout - time_spent)
|
|
1112
1120
|
else:
|
|
1113
1121
|
# The connection isn't subscribed to any channels or patterns,
|
|
@@ -1397,7 +1405,7 @@ class Pipeline(Redis):
|
|
|
1397
1405
|
conn = self.connection
|
|
1398
1406
|
# if this is the first call, we need a connection
|
|
1399
1407
|
if not conn:
|
|
1400
|
-
conn = self.connection_pool.get_connection(
|
|
1408
|
+
conn = self.connection_pool.get_connection()
|
|
1401
1409
|
self.connection = conn
|
|
1402
1410
|
|
|
1403
1411
|
return conn.retry.call_with_retry(
|
|
@@ -1516,8 +1524,7 @@ class Pipeline(Redis):
|
|
|
1516
1524
|
def annotate_exception(self, exception, number, command):
|
|
1517
1525
|
cmd = " ".join(map(safe_str, command))
|
|
1518
1526
|
msg = (
|
|
1519
|
-
f"Command # {number} ({cmd}) of pipeline "
|
|
1520
|
-
f"caused error: {exception.args[0]}"
|
|
1527
|
+
f"Command # {number} ({cmd}) of pipeline caused error: {exception.args[0]}"
|
|
1521
1528
|
)
|
|
1522
1529
|
exception.args = (msg,) + exception.args[1:]
|
|
1523
1530
|
|
|
@@ -1583,7 +1590,7 @@ class Pipeline(Redis):
|
|
|
1583
1590
|
|
|
1584
1591
|
conn = self.connection
|
|
1585
1592
|
if not conn:
|
|
1586
|
-
conn = self.connection_pool.get_connection(
|
|
1593
|
+
conn = self.connection_pool.get_connection()
|
|
1587
1594
|
# assign to self.connection so reset() releases the connection
|
|
1588
1595
|
# back to the pool after we're done
|
|
1589
1596
|
self.connection = conn
|