redis 6.4.0__py3-none-any.whl → 7.0.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 +1 -1
- redis/_parsers/base.py +193 -8
- redis/_parsers/helpers.py +64 -6
- redis/_parsers/hiredis.py +16 -10
- redis/_parsers/resp3.py +11 -5
- redis/asyncio/client.py +65 -8
- redis/asyncio/cluster.py +57 -5
- redis/asyncio/connection.py +62 -2
- redis/asyncio/http/__init__.py +0 -0
- redis/asyncio/http/http_client.py +265 -0
- redis/asyncio/multidb/__init__.py +0 -0
- redis/asyncio/multidb/client.py +530 -0
- redis/asyncio/multidb/command_executor.py +339 -0
- redis/asyncio/multidb/config.py +210 -0
- redis/asyncio/multidb/database.py +69 -0
- redis/asyncio/multidb/event.py +84 -0
- redis/asyncio/multidb/failover.py +125 -0
- redis/asyncio/multidb/failure_detector.py +38 -0
- redis/asyncio/multidb/healthcheck.py +285 -0
- redis/background.py +204 -0
- redis/cache.py +1 -0
- redis/client.py +97 -16
- redis/cluster.py +14 -3
- redis/commands/core.py +348 -313
- redis/commands/helpers.py +0 -20
- redis/commands/json/commands.py +2 -2
- redis/commands/search/__init__.py +2 -2
- redis/commands/search/aggregation.py +24 -26
- redis/commands/search/commands.py +10 -10
- redis/commands/search/field.py +2 -2
- redis/commands/search/query.py +23 -23
- redis/commands/vectorset/__init__.py +1 -1
- redis/commands/vectorset/commands.py +43 -25
- redis/commands/vectorset/utils.py +40 -4
- redis/connection.py +1257 -83
- redis/data_structure.py +81 -0
- redis/event.py +84 -10
- redis/exceptions.py +8 -0
- redis/http/__init__.py +0 -0
- redis/http/http_client.py +425 -0
- redis/maint_notifications.py +810 -0
- redis/multidb/__init__.py +0 -0
- redis/multidb/circuit.py +144 -0
- redis/multidb/client.py +526 -0
- redis/multidb/command_executor.py +350 -0
- redis/multidb/config.py +207 -0
- redis/multidb/database.py +130 -0
- redis/multidb/event.py +89 -0
- redis/multidb/exception.py +17 -0
- redis/multidb/failover.py +125 -0
- redis/multidb/failure_detector.py +104 -0
- redis/multidb/healthcheck.py +282 -0
- redis/retry.py +14 -1
- redis/utils.py +34 -0
- {redis-6.4.0.dist-info → redis-7.0.0.dist-info}/METADATA +7 -4
- redis-7.0.0.dist-info/RECORD +105 -0
- redis-6.4.0.dist-info/RECORD +0 -78
- {redis-6.4.0.dist-info → redis-7.0.0.dist-info}/WHEEL +0 -0
- {redis-6.4.0.dist-info → redis-7.0.0.dist-info}/licenses/LICENSE +0 -0
redis/client.py
CHANGED
|
@@ -56,6 +56,9 @@ from redis.exceptions import (
|
|
|
56
56
|
WatchError,
|
|
57
57
|
)
|
|
58
58
|
from redis.lock import Lock
|
|
59
|
+
from redis.maint_notifications import (
|
|
60
|
+
MaintNotificationsConfig,
|
|
61
|
+
)
|
|
59
62
|
from redis.retry import Retry
|
|
60
63
|
from redis.utils import (
|
|
61
64
|
_set_info_logger,
|
|
@@ -220,6 +223,8 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
220
223
|
ssl_keyfile: Optional[str] = None,
|
|
221
224
|
ssl_certfile: Optional[str] = None,
|
|
222
225
|
ssl_cert_reqs: Union[str, "ssl.VerifyMode"] = "required",
|
|
226
|
+
ssl_include_verify_flags: Optional[List["ssl.VerifyFlags"]] = None,
|
|
227
|
+
ssl_exclude_verify_flags: Optional[List["ssl.VerifyFlags"]] = None,
|
|
223
228
|
ssl_ca_certs: Optional[str] = None,
|
|
224
229
|
ssl_ca_path: Optional[str] = None,
|
|
225
230
|
ssl_ca_data: Optional[str] = None,
|
|
@@ -244,6 +249,7 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
244
249
|
cache: Optional[CacheInterface] = None,
|
|
245
250
|
cache_config: Optional[CacheConfig] = None,
|
|
246
251
|
event_dispatcher: Optional[EventDispatcher] = None,
|
|
252
|
+
maint_notifications_config: Optional[MaintNotificationsConfig] = None,
|
|
247
253
|
) -> None:
|
|
248
254
|
"""
|
|
249
255
|
Initialize a new Redis client.
|
|
@@ -271,6 +277,17 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
271
277
|
single_connection_client:
|
|
272
278
|
if `True`, connection pool is not used. In that case `Redis`
|
|
273
279
|
instance use is not thread safe.
|
|
280
|
+
decode_responses:
|
|
281
|
+
if `True`, the response will be decoded to utf-8.
|
|
282
|
+
Argument is ignored when connection_pool is provided.
|
|
283
|
+
maint_notifications_config:
|
|
284
|
+
configuration the pool to support maintenance notifications - see
|
|
285
|
+
`redis.maint_notifications.MaintNotificationsConfig` for details.
|
|
286
|
+
Only supported with RESP3
|
|
287
|
+
If not provided and protocol is RESP3, the maintenance notifications
|
|
288
|
+
will be enabled by default (logic is included in the connection pool
|
|
289
|
+
initialization).
|
|
290
|
+
Argument is ignored when connection_pool is provided.
|
|
274
291
|
"""
|
|
275
292
|
if event_dispatcher is None:
|
|
276
293
|
self._event_dispatcher = EventDispatcher()
|
|
@@ -325,6 +342,8 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
325
342
|
"ssl_keyfile": ssl_keyfile,
|
|
326
343
|
"ssl_certfile": ssl_certfile,
|
|
327
344
|
"ssl_cert_reqs": ssl_cert_reqs,
|
|
345
|
+
"ssl_include_verify_flags": ssl_include_verify_flags,
|
|
346
|
+
"ssl_exclude_verify_flags": ssl_exclude_verify_flags,
|
|
328
347
|
"ssl_ca_certs": ssl_ca_certs,
|
|
329
348
|
"ssl_ca_data": ssl_ca_data,
|
|
330
349
|
"ssl_check_hostname": ssl_check_hostname,
|
|
@@ -345,6 +364,22 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
345
364
|
"cache_config": cache_config,
|
|
346
365
|
}
|
|
347
366
|
)
|
|
367
|
+
maint_notifications_enabled = (
|
|
368
|
+
maint_notifications_config and maint_notifications_config.enabled
|
|
369
|
+
)
|
|
370
|
+
if maint_notifications_enabled and protocol not in [
|
|
371
|
+
3,
|
|
372
|
+
"3",
|
|
373
|
+
]:
|
|
374
|
+
raise RedisError(
|
|
375
|
+
"Maintenance notifications handlers on connection are only supported with RESP version 3"
|
|
376
|
+
)
|
|
377
|
+
if maint_notifications_config:
|
|
378
|
+
kwargs.update(
|
|
379
|
+
{
|
|
380
|
+
"maint_notifications_config": maint_notifications_config,
|
|
381
|
+
}
|
|
382
|
+
)
|
|
348
383
|
connection_pool = ConnectionPool(**kwargs)
|
|
349
384
|
self._event_dispatcher.dispatch(
|
|
350
385
|
AfterPooledConnectionsInstantiationEvent(
|
|
@@ -566,7 +601,8 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
566
601
|
|
|
567
602
|
def client(self):
|
|
568
603
|
return self.__class__(
|
|
569
|
-
connection_pool=self.connection_pool,
|
|
604
|
+
connection_pool=self.connection_pool,
|
|
605
|
+
single_connection_client=True,
|
|
570
606
|
)
|
|
571
607
|
|
|
572
608
|
def __enter__(self):
|
|
@@ -635,7 +671,11 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
635
671
|
),
|
|
636
672
|
lambda _: self._close_connection(conn),
|
|
637
673
|
)
|
|
674
|
+
|
|
638
675
|
finally:
|
|
676
|
+
if conn and conn.should_reconnect():
|
|
677
|
+
self._close_connection(conn)
|
|
678
|
+
conn.connect()
|
|
639
679
|
if self._single_connection_client:
|
|
640
680
|
self.single_connection_lock.release()
|
|
641
681
|
if not self.connection:
|
|
@@ -686,11 +726,7 @@ class Monitor:
|
|
|
686
726
|
self.connection = self.connection_pool.get_connection()
|
|
687
727
|
|
|
688
728
|
def __enter__(self):
|
|
689
|
-
self.
|
|
690
|
-
# check that monitor returns 'OK', but don't return it to user
|
|
691
|
-
response = self.connection.read_response()
|
|
692
|
-
if not bool_ok(response):
|
|
693
|
-
raise RedisError(f"MONITOR failed: {response}")
|
|
729
|
+
self._start_monitor()
|
|
694
730
|
return self
|
|
695
731
|
|
|
696
732
|
def __exit__(self, *args):
|
|
@@ -700,8 +736,13 @@ class Monitor:
|
|
|
700
736
|
def next_command(self):
|
|
701
737
|
"""Parse the response from a monitor command"""
|
|
702
738
|
response = self.connection.read_response()
|
|
739
|
+
|
|
740
|
+
if response is None:
|
|
741
|
+
return None
|
|
742
|
+
|
|
703
743
|
if isinstance(response, bytes):
|
|
704
744
|
response = self.connection.encoder.decode(response, force=True)
|
|
745
|
+
|
|
705
746
|
command_time, command_data = response.split(" ", 1)
|
|
706
747
|
m = self.monitor_re.match(command_data)
|
|
707
748
|
db_id, client_info, command = m.groups()
|
|
@@ -737,6 +778,14 @@ class Monitor:
|
|
|
737
778
|
while True:
|
|
738
779
|
yield self.next_command()
|
|
739
780
|
|
|
781
|
+
def _start_monitor(self):
|
|
782
|
+
self.connection.send_command("MONITOR")
|
|
783
|
+
# check that monitor returns 'OK', but don't return it to user
|
|
784
|
+
response = self.connection.read_response()
|
|
785
|
+
|
|
786
|
+
if not bool_ok(response):
|
|
787
|
+
raise RedisError(f"MONITOR failed: {response}")
|
|
788
|
+
|
|
740
789
|
|
|
741
790
|
class PubSub:
|
|
742
791
|
"""
|
|
@@ -881,7 +930,7 @@ class PubSub:
|
|
|
881
930
|
"""
|
|
882
931
|
ttl = 10
|
|
883
932
|
conn = self.connection
|
|
884
|
-
while self.health_check_response_counter > 0 and ttl > 0:
|
|
933
|
+
while conn and self.health_check_response_counter > 0 and ttl > 0:
|
|
885
934
|
if self._execute(conn, conn.can_read, timeout=conn.socket_timeout):
|
|
886
935
|
response = self._execute(conn, conn.read_response)
|
|
887
936
|
if self.is_health_check_response(response):
|
|
@@ -911,11 +960,17 @@ class PubSub:
|
|
|
911
960
|
called by the # connection to resubscribe us to any channels and
|
|
912
961
|
patterns we were previously listening to
|
|
913
962
|
"""
|
|
914
|
-
|
|
963
|
+
|
|
964
|
+
if conn.should_reconnect():
|
|
965
|
+
self._reconnect(conn)
|
|
966
|
+
|
|
967
|
+
response = conn.retry.call_with_retry(
|
|
915
968
|
lambda: command(*args, **kwargs),
|
|
916
969
|
lambda _: self._reconnect(conn),
|
|
917
970
|
)
|
|
918
971
|
|
|
972
|
+
return response
|
|
973
|
+
|
|
919
974
|
def parse_response(self, block=True, timeout=0):
|
|
920
975
|
"""Parse the response from a publish/subscribe command"""
|
|
921
976
|
conn = self.connection
|
|
@@ -1125,6 +1180,7 @@ class PubSub:
|
|
|
1125
1180
|
return None
|
|
1126
1181
|
|
|
1127
1182
|
response = self.parse_response(block=(timeout is None), timeout=timeout)
|
|
1183
|
+
|
|
1128
1184
|
if response:
|
|
1129
1185
|
return self.handle_message(response, ignore_subscribe_messages)
|
|
1130
1186
|
return None
|
|
@@ -1133,7 +1189,10 @@ class PubSub:
|
|
|
1133
1189
|
|
|
1134
1190
|
def ping(self, message: Union[str, None] = None) -> bool:
|
|
1135
1191
|
"""
|
|
1136
|
-
Ping the Redis server
|
|
1192
|
+
Ping the Redis server to test connectivity.
|
|
1193
|
+
|
|
1194
|
+
Sends a PING command to the Redis server and returns True if the server
|
|
1195
|
+
responds with "PONG".
|
|
1137
1196
|
"""
|
|
1138
1197
|
args = ["PING", message] if message is not None else ["PING"]
|
|
1139
1198
|
return self.execute_command(*args)
|
|
@@ -1148,6 +1207,7 @@ class PubSub:
|
|
|
1148
1207
|
return None
|
|
1149
1208
|
if isinstance(response, bytes):
|
|
1150
1209
|
response = [b"pong", response] if response != b"PONG" else [b"pong", b""]
|
|
1210
|
+
|
|
1151
1211
|
message_type = str_if_bytes(response[0])
|
|
1152
1212
|
if message_type == "pmessage":
|
|
1153
1213
|
message = {
|
|
@@ -1217,6 +1277,8 @@ class PubSub:
|
|
|
1217
1277
|
sleep_time: float = 0.0,
|
|
1218
1278
|
daemon: bool = False,
|
|
1219
1279
|
exception_handler: Optional[Callable] = None,
|
|
1280
|
+
pubsub=None,
|
|
1281
|
+
sharded_pubsub: bool = False,
|
|
1220
1282
|
) -> "PubSubWorkerThread":
|
|
1221
1283
|
for channel, handler in self.channels.items():
|
|
1222
1284
|
if handler is None:
|
|
@@ -1230,8 +1292,13 @@ class PubSub:
|
|
|
1230
1292
|
f"Shard Channel: '{s_channel}' has no handler registered"
|
|
1231
1293
|
)
|
|
1232
1294
|
|
|
1295
|
+
pubsub = self if pubsub is None else pubsub
|
|
1233
1296
|
thread = PubSubWorkerThread(
|
|
1234
|
-
|
|
1297
|
+
pubsub,
|
|
1298
|
+
sleep_time,
|
|
1299
|
+
daemon=daemon,
|
|
1300
|
+
exception_handler=exception_handler,
|
|
1301
|
+
sharded_pubsub=sharded_pubsub,
|
|
1235
1302
|
)
|
|
1236
1303
|
thread.start()
|
|
1237
1304
|
return thread
|
|
@@ -1246,12 +1313,14 @@ class PubSubWorkerThread(threading.Thread):
|
|
|
1246
1313
|
exception_handler: Union[
|
|
1247
1314
|
Callable[[Exception, "PubSub", "PubSubWorkerThread"], None], None
|
|
1248
1315
|
] = None,
|
|
1316
|
+
sharded_pubsub: bool = False,
|
|
1249
1317
|
):
|
|
1250
1318
|
super().__init__()
|
|
1251
1319
|
self.daemon = daemon
|
|
1252
1320
|
self.pubsub = pubsub
|
|
1253
1321
|
self.sleep_time = sleep_time
|
|
1254
1322
|
self.exception_handler = exception_handler
|
|
1323
|
+
self.sharded_pubsub = sharded_pubsub
|
|
1255
1324
|
self._running = threading.Event()
|
|
1256
1325
|
|
|
1257
1326
|
def run(self) -> None:
|
|
@@ -1262,7 +1331,14 @@ class PubSubWorkerThread(threading.Thread):
|
|
|
1262
1331
|
sleep_time = self.sleep_time
|
|
1263
1332
|
while self._running.is_set():
|
|
1264
1333
|
try:
|
|
1265
|
-
|
|
1334
|
+
if not self.sharded_pubsub:
|
|
1335
|
+
pubsub.get_message(
|
|
1336
|
+
ignore_subscribe_messages=True, timeout=sleep_time
|
|
1337
|
+
)
|
|
1338
|
+
else:
|
|
1339
|
+
pubsub.get_sharded_message(
|
|
1340
|
+
ignore_subscribe_messages=True, timeout=sleep_time
|
|
1341
|
+
)
|
|
1266
1342
|
except BaseException as e:
|
|
1267
1343
|
if self.exception_handler is None:
|
|
1268
1344
|
raise
|
|
@@ -1351,6 +1427,7 @@ class Pipeline(Redis):
|
|
|
1351
1427
|
# clean up the other instance attributes
|
|
1352
1428
|
self.watching = False
|
|
1353
1429
|
self.explicit_transaction = False
|
|
1430
|
+
|
|
1354
1431
|
# we can safely return the connection to the pool here since we're
|
|
1355
1432
|
# sure we're no longer WATCHing anything
|
|
1356
1433
|
if self.connection:
|
|
@@ -1510,6 +1587,7 @@ class Pipeline(Redis):
|
|
|
1510
1587
|
if command_name in self.response_callbacks:
|
|
1511
1588
|
r = self.response_callbacks[command_name](r, **options)
|
|
1512
1589
|
data.append(r)
|
|
1590
|
+
|
|
1513
1591
|
return data
|
|
1514
1592
|
|
|
1515
1593
|
def _execute_pipeline(self, connection, commands, raise_on_error):
|
|
@@ -1517,16 +1595,17 @@ class Pipeline(Redis):
|
|
|
1517
1595
|
all_cmds = connection.pack_commands([args for args, _ in commands])
|
|
1518
1596
|
connection.send_packed_command(all_cmds)
|
|
1519
1597
|
|
|
1520
|
-
|
|
1598
|
+
responses = []
|
|
1521
1599
|
for args, options in commands:
|
|
1522
1600
|
try:
|
|
1523
|
-
|
|
1601
|
+
responses.append(self.parse_response(connection, args[0], **options))
|
|
1524
1602
|
except ResponseError as e:
|
|
1525
|
-
|
|
1603
|
+
responses.append(e)
|
|
1526
1604
|
|
|
1527
1605
|
if raise_on_error:
|
|
1528
|
-
self.raise_first_error(commands,
|
|
1529
|
-
|
|
1606
|
+
self.raise_first_error(commands, responses)
|
|
1607
|
+
|
|
1608
|
+
return responses
|
|
1530
1609
|
|
|
1531
1610
|
def raise_first_error(self, commands, response):
|
|
1532
1611
|
for i, r in enumerate(response):
|
|
@@ -1611,6 +1690,8 @@ class Pipeline(Redis):
|
|
|
1611
1690
|
lambda error: self._disconnect_raise_on_watching(conn, error),
|
|
1612
1691
|
)
|
|
1613
1692
|
finally:
|
|
1693
|
+
# in reset() the connection is disconnected before returned to the pool if
|
|
1694
|
+
# it is marked for reconnect.
|
|
1614
1695
|
self.reset()
|
|
1615
1696
|
|
|
1616
1697
|
def discard(self):
|
redis/cluster.py
CHANGED
|
@@ -50,6 +50,7 @@ from redis.exceptions import (
|
|
|
50
50
|
WatchError,
|
|
51
51
|
)
|
|
52
52
|
from redis.lock import Lock
|
|
53
|
+
from redis.maint_notifications import MaintNotificationsConfig
|
|
53
54
|
from redis.retry import Retry
|
|
54
55
|
from redis.utils import (
|
|
55
56
|
deprecated_args,
|
|
@@ -170,6 +171,7 @@ REDIS_ALLOWED_KEYS = (
|
|
|
170
171
|
"redis_connect_func",
|
|
171
172
|
"password",
|
|
172
173
|
"port",
|
|
174
|
+
"timeout",
|
|
173
175
|
"queue_class",
|
|
174
176
|
"retry",
|
|
175
177
|
"retry_on_timeout",
|
|
@@ -183,6 +185,8 @@ REDIS_ALLOWED_KEYS = (
|
|
|
183
185
|
"ssl_ca_data",
|
|
184
186
|
"ssl_certfile",
|
|
185
187
|
"ssl_cert_reqs",
|
|
188
|
+
"ssl_include_verify_flags",
|
|
189
|
+
"ssl_exclude_verify_flags",
|
|
186
190
|
"ssl_keyfile",
|
|
187
191
|
"ssl_password",
|
|
188
192
|
"ssl_check_hostname",
|
|
@@ -692,6 +696,7 @@ class RedisCluster(AbstractRedisCluster, RedisClusterCommands):
|
|
|
692
696
|
self._event_dispatcher = EventDispatcher()
|
|
693
697
|
else:
|
|
694
698
|
self._event_dispatcher = event_dispatcher
|
|
699
|
+
self.startup_nodes = startup_nodes
|
|
695
700
|
self.nodes_manager = NodesManager(
|
|
696
701
|
startup_nodes=startup_nodes,
|
|
697
702
|
from_url=from_url,
|
|
@@ -1659,6 +1664,11 @@ class NodesManager:
|
|
|
1659
1664
|
backoff=NoBackoff(), retries=0, supported_errors=(ConnectionError,)
|
|
1660
1665
|
)
|
|
1661
1666
|
|
|
1667
|
+
protocol = kwargs.get("protocol", None)
|
|
1668
|
+
if protocol in [3, "3"]:
|
|
1669
|
+
kwargs.update(
|
|
1670
|
+
{"maint_notifications_config": MaintNotificationsConfig(enabled=False)}
|
|
1671
|
+
)
|
|
1662
1672
|
if self.from_url:
|
|
1663
1673
|
# Create a redis node with a costumed connection pool
|
|
1664
1674
|
kwargs.update({"host": host})
|
|
@@ -2716,8 +2726,8 @@ class PipelineStrategy(AbstractStrategy):
|
|
|
2716
2726
|
|
|
2717
2727
|
If one of the retryable exceptions has been thrown we assume that:
|
|
2718
2728
|
- connection_pool was disconnected
|
|
2719
|
-
- connection_pool was
|
|
2720
|
-
-
|
|
2729
|
+
- connection_pool was reset
|
|
2730
|
+
- refresh_table_asap set to True
|
|
2721
2731
|
|
|
2722
2732
|
It will try the number of times specified by
|
|
2723
2733
|
the retries in config option "self.retry"
|
|
@@ -3161,7 +3171,8 @@ class TransactionStrategy(AbstractStrategy):
|
|
|
3161
3171
|
self._nodes_manager.initialize()
|
|
3162
3172
|
self.reinitialize_counter = 0
|
|
3163
3173
|
else:
|
|
3164
|
-
|
|
3174
|
+
if isinstance(error, AskError):
|
|
3175
|
+
self._nodes_manager.update_moved_exception(error)
|
|
3165
3176
|
|
|
3166
3177
|
self._executing = False
|
|
3167
3178
|
|