redis 5.2.1__py3-none-any.whl → 5.3.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/asyncio/client.py +49 -12
- redis/asyncio/cluster.py +101 -12
- redis/asyncio/connection.py +78 -11
- redis/auth/__init__.py +0 -0
- redis/auth/err.py +31 -0
- redis/auth/idp.py +28 -0
- redis/auth/token.py +126 -0
- redis/auth/token_manager.py +370 -0
- redis/backoff.py +15 -0
- redis/client.py +116 -56
- redis/cluster.py +157 -33
- redis/connection.py +103 -11
- redis/credentials.py +40 -1
- redis/event.py +394 -0
- redis/typing.py +1 -1
- redis/utils.py +65 -0
- {redis-5.2.1.dist-info → redis-5.3.0.dist-info}/METADATA +2 -1
- {redis-5.2.1.dist-info → redis-5.3.0.dist-info}/RECORD +21 -15
- {redis-5.2.1.dist-info → redis-5.3.0.dist-info}/LICENSE +0 -0
- {redis-5.2.1.dist-info → redis-5.3.0.dist-info}/WHEEL +0 -0
- {redis-5.2.1.dist-info → redis-5.3.0.dist-info}/top_level.txt +0 -0
redis/client.py
CHANGED
|
@@ -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 (
|
|
@@ -27,6 +37,13 @@ from redis.connection import (
|
|
|
27
37
|
UnixDomainSocketConnection,
|
|
28
38
|
)
|
|
29
39
|
from redis.credentials import CredentialProvider
|
|
40
|
+
from redis.event import (
|
|
41
|
+
AfterPooledConnectionsInstantiationEvent,
|
|
42
|
+
AfterPubSubConnectionInstantiationEvent,
|
|
43
|
+
AfterSingleConnectionInstantiationEvent,
|
|
44
|
+
ClientType,
|
|
45
|
+
EventDispatcher,
|
|
46
|
+
)
|
|
30
47
|
from redis.exceptions import (
|
|
31
48
|
ConnectionError,
|
|
32
49
|
ExecAbortError,
|
|
@@ -46,6 +63,11 @@ from redis.utils import (
|
|
|
46
63
|
str_if_bytes,
|
|
47
64
|
)
|
|
48
65
|
|
|
66
|
+
if TYPE_CHECKING:
|
|
67
|
+
import ssl
|
|
68
|
+
|
|
69
|
+
import OpenSSL
|
|
70
|
+
|
|
49
71
|
SYM_EMPTY = b""
|
|
50
72
|
EMPTY_RESPONSE = "EMPTY_RESPONSE"
|
|
51
73
|
|
|
@@ -168,51 +190,52 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
168
190
|
|
|
169
191
|
def __init__(
|
|
170
192
|
self,
|
|
171
|
-
host="localhost",
|
|
172
|
-
port=6379,
|
|
173
|
-
db=0,
|
|
174
|
-
password=None,
|
|
175
|
-
socket_timeout=None,
|
|
176
|
-
socket_connect_timeout=None,
|
|
177
|
-
socket_keepalive=None,
|
|
178
|
-
socket_keepalive_options=None,
|
|
179
|
-
connection_pool=None,
|
|
180
|
-
unix_socket_path=None,
|
|
181
|
-
encoding="utf-8",
|
|
182
|
-
encoding_errors="strict",
|
|
183
|
-
charset=None,
|
|
184
|
-
errors=None,
|
|
185
|
-
decode_responses=False,
|
|
186
|
-
retry_on_timeout=False,
|
|
187
|
-
retry_on_error=None,
|
|
188
|
-
ssl=False,
|
|
189
|
-
ssl_keyfile=None,
|
|
190
|
-
ssl_certfile=None,
|
|
191
|
-
ssl_cert_reqs="required",
|
|
192
|
-
ssl_ca_certs=None,
|
|
193
|
-
ssl_ca_path=None,
|
|
194
|
-
ssl_ca_data=None,
|
|
195
|
-
ssl_check_hostname=False,
|
|
196
|
-
ssl_password=None,
|
|
197
|
-
ssl_validate_ocsp=False,
|
|
198
|
-
ssl_validate_ocsp_stapled=False,
|
|
199
|
-
ssl_ocsp_context=None,
|
|
200
|
-
ssl_ocsp_expected_cert=None,
|
|
201
|
-
ssl_min_version=None,
|
|
202
|
-
ssl_ciphers=None,
|
|
203
|
-
max_connections=None,
|
|
204
|
-
single_connection_client=False,
|
|
205
|
-
health_check_interval=0,
|
|
206
|
-
client_name=None,
|
|
207
|
-
lib_name="redis-py",
|
|
208
|
-
lib_version=get_lib_version(),
|
|
209
|
-
username=None,
|
|
210
|
-
retry=None,
|
|
211
|
-
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,
|
|
212
234
|
credential_provider: Optional[CredentialProvider] = None,
|
|
213
235
|
protocol: Optional[int] = 2,
|
|
214
236
|
cache: Optional[CacheInterface] = None,
|
|
215
237
|
cache_config: Optional[CacheConfig] = None,
|
|
238
|
+
event_dispatcher: Optional[EventDispatcher] = None,
|
|
216
239
|
) -> None:
|
|
217
240
|
"""
|
|
218
241
|
Initialize a new Redis client.
|
|
@@ -227,6 +250,10 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
227
250
|
if `True`, connection pool is not used. In that case `Redis`
|
|
228
251
|
instance use is not thread safe.
|
|
229
252
|
"""
|
|
253
|
+
if event_dispatcher is None:
|
|
254
|
+
self._event_dispatcher = EventDispatcher()
|
|
255
|
+
else:
|
|
256
|
+
self._event_dispatcher = event_dispatcher
|
|
230
257
|
if not connection_pool:
|
|
231
258
|
if charset is not None:
|
|
232
259
|
warnings.warn(
|
|
@@ -313,9 +340,19 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
313
340
|
}
|
|
314
341
|
)
|
|
315
342
|
connection_pool = ConnectionPool(**kwargs)
|
|
343
|
+
self._event_dispatcher.dispatch(
|
|
344
|
+
AfterPooledConnectionsInstantiationEvent(
|
|
345
|
+
[connection_pool], ClientType.SYNC, credential_provider
|
|
346
|
+
)
|
|
347
|
+
)
|
|
316
348
|
self.auto_close_connection_pool = True
|
|
317
349
|
else:
|
|
318
350
|
self.auto_close_connection_pool = False
|
|
351
|
+
self._event_dispatcher.dispatch(
|
|
352
|
+
AfterPooledConnectionsInstantiationEvent(
|
|
353
|
+
[connection_pool], ClientType.SYNC, credential_provider
|
|
354
|
+
)
|
|
355
|
+
)
|
|
319
356
|
|
|
320
357
|
self.connection_pool = connection_pool
|
|
321
358
|
|
|
@@ -325,9 +362,16 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
325
362
|
]:
|
|
326
363
|
raise RedisError("Client caching is only supported with RESP version 3")
|
|
327
364
|
|
|
365
|
+
self.single_connection_lock = threading.Lock()
|
|
328
366
|
self.connection = None
|
|
329
|
-
|
|
330
|
-
|
|
367
|
+
self._single_connection_client = single_connection_client
|
|
368
|
+
if self._single_connection_client:
|
|
369
|
+
self.connection = self.connection_pool.get_connection()
|
|
370
|
+
self._event_dispatcher.dispatch(
|
|
371
|
+
AfterSingleConnectionInstantiationEvent(
|
|
372
|
+
self.connection, ClientType.SYNC, self.single_connection_lock
|
|
373
|
+
)
|
|
374
|
+
)
|
|
331
375
|
|
|
332
376
|
self.response_callbacks = CaseInsensitiveDict(_RedisCallbacks)
|
|
333
377
|
|
|
@@ -500,7 +544,9 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
500
544
|
subscribe to channels and listen for messages that get published to
|
|
501
545
|
them.
|
|
502
546
|
"""
|
|
503
|
-
return PubSub(
|
|
547
|
+
return PubSub(
|
|
548
|
+
self.connection_pool, event_dispatcher=self._event_dispatcher, **kwargs
|
|
549
|
+
)
|
|
504
550
|
|
|
505
551
|
def monitor(self):
|
|
506
552
|
return Monitor(self.connection_pool)
|
|
@@ -519,7 +565,7 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
519
565
|
def __del__(self):
|
|
520
566
|
self.close()
|
|
521
567
|
|
|
522
|
-
def close(self):
|
|
568
|
+
def close(self) -> None:
|
|
523
569
|
# In case a connection property does not yet exist
|
|
524
570
|
# (due to a crash earlier in the Redis() constructor), return
|
|
525
571
|
# immediately as there is nothing to clean-up.
|
|
@@ -562,7 +608,10 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
562
608
|
"""Execute a command and return a parsed response"""
|
|
563
609
|
pool = self.connection_pool
|
|
564
610
|
command_name = args[0]
|
|
565
|
-
conn = self.connection or pool.get_connection(
|
|
611
|
+
conn = self.connection or pool.get_connection()
|
|
612
|
+
|
|
613
|
+
if self._single_connection_client:
|
|
614
|
+
self.single_connection_lock.acquire()
|
|
566
615
|
try:
|
|
567
616
|
return conn.retry.call_with_retry(
|
|
568
617
|
lambda: self._send_command_parse_response(
|
|
@@ -571,6 +620,8 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
571
620
|
lambda error: self._disconnect_raise(conn, error),
|
|
572
621
|
)
|
|
573
622
|
finally:
|
|
623
|
+
if self._single_connection_client:
|
|
624
|
+
self.single_connection_lock.release()
|
|
574
625
|
if not self.connection:
|
|
575
626
|
pool.release(conn)
|
|
576
627
|
|
|
@@ -616,7 +667,7 @@ class Monitor:
|
|
|
616
667
|
|
|
617
668
|
def __init__(self, connection_pool):
|
|
618
669
|
self.connection_pool = connection_pool
|
|
619
|
-
self.connection = self.connection_pool.get_connection(
|
|
670
|
+
self.connection = self.connection_pool.get_connection()
|
|
620
671
|
|
|
621
672
|
def __enter__(self):
|
|
622
673
|
self.connection.send_command("MONITOR")
|
|
@@ -691,6 +742,7 @@ class PubSub:
|
|
|
691
742
|
ignore_subscribe_messages: bool = False,
|
|
692
743
|
encoder: Optional["Encoder"] = None,
|
|
693
744
|
push_handler_func: Union[None, Callable[[str], None]] = None,
|
|
745
|
+
event_dispatcher: Optional["EventDispatcher"] = None,
|
|
694
746
|
):
|
|
695
747
|
self.connection_pool = connection_pool
|
|
696
748
|
self.shard_hint = shard_hint
|
|
@@ -701,6 +753,11 @@ class PubSub:
|
|
|
701
753
|
# to lookup channel and pattern names for callback handlers.
|
|
702
754
|
self.encoder = encoder
|
|
703
755
|
self.push_handler_func = push_handler_func
|
|
756
|
+
if event_dispatcher is None:
|
|
757
|
+
self._event_dispatcher = EventDispatcher()
|
|
758
|
+
else:
|
|
759
|
+
self._event_dispatcher = event_dispatcher
|
|
760
|
+
self._lock = threading.Lock()
|
|
704
761
|
if self.encoder is None:
|
|
705
762
|
self.encoder = self.connection_pool.get_encoder()
|
|
706
763
|
self.health_check_response_b = self.encoder.encode(self.HEALTH_CHECK_MESSAGE)
|
|
@@ -783,19 +840,23 @@ class PubSub:
|
|
|
783
840
|
# subscribed to one or more channels
|
|
784
841
|
|
|
785
842
|
if self.connection is None:
|
|
786
|
-
self.connection = self.connection_pool.get_connection(
|
|
787
|
-
"pubsub", self.shard_hint
|
|
788
|
-
)
|
|
843
|
+
self.connection = self.connection_pool.get_connection()
|
|
789
844
|
# register a callback that re-subscribes to any channels we
|
|
790
845
|
# were listening to when we were disconnected
|
|
791
846
|
self.connection.register_connect_callback(self.on_connect)
|
|
792
847
|
if self.push_handler_func is not None and not HIREDIS_AVAILABLE:
|
|
793
848
|
self.connection._parser.set_pubsub_push_handler(self.push_handler_func)
|
|
849
|
+
self._event_dispatcher.dispatch(
|
|
850
|
+
AfterPubSubConnectionInstantiationEvent(
|
|
851
|
+
self.connection, self.connection_pool, ClientType.SYNC, self._lock
|
|
852
|
+
)
|
|
853
|
+
)
|
|
794
854
|
connection = self.connection
|
|
795
855
|
kwargs = {"check_health": not self.subscribed}
|
|
796
856
|
if not self.subscribed:
|
|
797
857
|
self.clean_health_check_responses()
|
|
798
|
-
self.
|
|
858
|
+
with self._lock:
|
|
859
|
+
self._execute(connection, connection.send_command, *args, **kwargs)
|
|
799
860
|
|
|
800
861
|
def clean_health_check_responses(self) -> None:
|
|
801
862
|
"""
|
|
@@ -1334,7 +1395,7 @@ class Pipeline(Redis):
|
|
|
1334
1395
|
conn = self.connection
|
|
1335
1396
|
# if this is the first call, we need a connection
|
|
1336
1397
|
if not conn:
|
|
1337
|
-
conn = self.connection_pool.get_connection(
|
|
1398
|
+
conn = self.connection_pool.get_connection()
|
|
1338
1399
|
self.connection = conn
|
|
1339
1400
|
|
|
1340
1401
|
return conn.retry.call_with_retry(
|
|
@@ -1503,11 +1564,10 @@ class Pipeline(Redis):
|
|
|
1503
1564
|
conn.retry_on_error is None
|
|
1504
1565
|
or isinstance(error, tuple(conn.retry_on_error)) is False
|
|
1505
1566
|
):
|
|
1506
|
-
|
|
1507
1567
|
self.reset()
|
|
1508
1568
|
raise error
|
|
1509
1569
|
|
|
1510
|
-
def execute(self, raise_on_error=True):
|
|
1570
|
+
def execute(self, raise_on_error: bool = True) -> List[Any]:
|
|
1511
1571
|
"""Execute all the commands in the current pipeline"""
|
|
1512
1572
|
stack = self.command_stack
|
|
1513
1573
|
if not stack and not self.watching:
|
|
@@ -1521,7 +1581,7 @@ class Pipeline(Redis):
|
|
|
1521
1581
|
|
|
1522
1582
|
conn = self.connection
|
|
1523
1583
|
if not conn:
|
|
1524
|
-
conn = self.connection_pool.get_connection(
|
|
1584
|
+
conn = self.connection_pool.get_connection()
|
|
1525
1585
|
# assign to self.connection so reset() releases the connection
|
|
1526
1586
|
# back to the pool after we're done
|
|
1527
1587
|
self.connection = conn
|