redis 6.0.0b1__py3-none-any.whl → 6.0.0b2__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/asyncio/client.py +8 -3
- redis/asyncio/cluster.py +7 -4
- redis/asyncio/connection.py +36 -12
- redis/asyncio/sentinel.py +2 -0
- redis/client.py +4 -2
- redis/cluster.py +3 -1
- redis/commands/core.py +1 -1
- redis/commands/redismodules.py +8 -0
- redis/commands/vectorset/__init__.py +46 -0
- redis/commands/vectorset/commands.py +367 -0
- redis/commands/vectorset/utils.py +94 -0
- redis/connection.py +39 -8
- redis/sentinel.py +2 -0
- redis/utils.py +7 -0
- {redis-6.0.0b1.dist-info → redis-6.0.0b2.dist-info}/METADATA +1 -1
- {redis-6.0.0b1.dist-info → redis-6.0.0b2.dist-info}/RECORD +19 -16
- {redis-6.0.0b1.dist-info → redis-6.0.0b2.dist-info}/WHEEL +0 -0
- {redis-6.0.0b1.dist-info → redis-6.0.0b2.dist-info}/licenses/LICENSE +0 -0
redis/__init__.py
CHANGED
redis/asyncio/client.py
CHANGED
|
@@ -77,12 +77,14 @@ from redis.utils import (
|
|
|
77
77
|
get_lib_version,
|
|
78
78
|
safe_str,
|
|
79
79
|
str_if_bytes,
|
|
80
|
+
truncate_text,
|
|
80
81
|
)
|
|
81
82
|
|
|
82
83
|
if TYPE_CHECKING and SSL_AVAILABLE:
|
|
83
|
-
from ssl import TLSVersion
|
|
84
|
+
from ssl import TLSVersion, VerifyMode
|
|
84
85
|
else:
|
|
85
86
|
TLSVersion = None
|
|
87
|
+
VerifyMode = None
|
|
86
88
|
|
|
87
89
|
PubSubHandler = Callable[[Dict[str, str]], Awaitable[None]]
|
|
88
90
|
_KeyT = TypeVar("_KeyT", bound=KeyT)
|
|
@@ -227,7 +229,7 @@ class Redis(
|
|
|
227
229
|
ssl: bool = False,
|
|
228
230
|
ssl_keyfile: Optional[str] = None,
|
|
229
231
|
ssl_certfile: Optional[str] = None,
|
|
230
|
-
ssl_cert_reqs: str = "required",
|
|
232
|
+
ssl_cert_reqs: Union[str, VerifyMode] = "required",
|
|
231
233
|
ssl_ca_certs: Optional[str] = None,
|
|
232
234
|
ssl_ca_data: Optional[str] = None,
|
|
233
235
|
ssl_check_hostname: bool = False,
|
|
@@ -1513,7 +1515,10 @@ class Pipeline(Redis): # lgtm [py/init-calls-subclass]
|
|
|
1513
1515
|
self, exception: Exception, number: int, command: Iterable[object]
|
|
1514
1516
|
) -> None:
|
|
1515
1517
|
cmd = " ".join(map(safe_str, command))
|
|
1516
|
-
msg =
|
|
1518
|
+
msg = (
|
|
1519
|
+
f"Command # {number} ({truncate_text(cmd)}) "
|
|
1520
|
+
"of pipeline caused error: {exception.args}"
|
|
1521
|
+
)
|
|
1517
1522
|
exception.args = (msg,) + exception.args[1:]
|
|
1518
1523
|
|
|
1519
1524
|
async def parse_response(
|
redis/asyncio/cluster.py
CHANGED
|
@@ -71,12 +71,14 @@ from redis.utils import (
|
|
|
71
71
|
get_lib_version,
|
|
72
72
|
safe_str,
|
|
73
73
|
str_if_bytes,
|
|
74
|
+
truncate_text,
|
|
74
75
|
)
|
|
75
76
|
|
|
76
77
|
if SSL_AVAILABLE:
|
|
77
|
-
from ssl import TLSVersion
|
|
78
|
+
from ssl import TLSVersion, VerifyMode
|
|
78
79
|
else:
|
|
79
80
|
TLSVersion = None
|
|
81
|
+
VerifyMode = None
|
|
80
82
|
|
|
81
83
|
TargetNodesT = TypeVar(
|
|
82
84
|
"TargetNodesT", str, "ClusterNode", List["ClusterNode"], Dict[Any, "ClusterNode"]
|
|
@@ -267,7 +269,7 @@ class RedisCluster(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterCommand
|
|
|
267
269
|
ssl: bool = False,
|
|
268
270
|
ssl_ca_certs: Optional[str] = None,
|
|
269
271
|
ssl_ca_data: Optional[str] = None,
|
|
270
|
-
ssl_cert_reqs: str = "required",
|
|
272
|
+
ssl_cert_reqs: Union[str, VerifyMode] = "required",
|
|
271
273
|
ssl_certfile: Optional[str] = None,
|
|
272
274
|
ssl_check_hostname: bool = False,
|
|
273
275
|
ssl_keyfile: Optional[str] = None,
|
|
@@ -1633,8 +1635,9 @@ class ClusterPipeline(AbstractRedis, AbstractRedisCluster, AsyncRedisClusterComm
|
|
|
1633
1635
|
if isinstance(result, Exception):
|
|
1634
1636
|
command = " ".join(map(safe_str, cmd.args))
|
|
1635
1637
|
msg = (
|
|
1636
|
-
f"Command # {cmd.position + 1}
|
|
1637
|
-
f"
|
|
1638
|
+
f"Command # {cmd.position + 1} "
|
|
1639
|
+
f"({truncate_text(command)}) "
|
|
1640
|
+
f"of pipeline caused error: {result.args}"
|
|
1638
1641
|
)
|
|
1639
1642
|
result.args = (msg,) + result.args[1:]
|
|
1640
1643
|
raise result
|
redis/asyncio/connection.py
CHANGED
|
@@ -293,6 +293,9 @@ class AbstractConnection:
|
|
|
293
293
|
|
|
294
294
|
async def connect(self):
|
|
295
295
|
"""Connects to the Redis server if not already connected"""
|
|
296
|
+
await self.connect_check_health(check_health=True)
|
|
297
|
+
|
|
298
|
+
async def connect_check_health(self, check_health: bool = True):
|
|
296
299
|
if self.is_connected:
|
|
297
300
|
return
|
|
298
301
|
try:
|
|
@@ -311,7 +314,7 @@ class AbstractConnection:
|
|
|
311
314
|
try:
|
|
312
315
|
if not self.redis_connect_func:
|
|
313
316
|
# Use the default on_connect function
|
|
314
|
-
await self.
|
|
317
|
+
await self.on_connect_check_health(check_health=check_health)
|
|
315
318
|
else:
|
|
316
319
|
# Use the passed function redis_connect_func
|
|
317
320
|
(
|
|
@@ -350,6 +353,9 @@ class AbstractConnection:
|
|
|
350
353
|
|
|
351
354
|
async def on_connect(self) -> None:
|
|
352
355
|
"""Initialize the connection, authenticate and select a database"""
|
|
356
|
+
await self.on_connect_check_health(check_health=True)
|
|
357
|
+
|
|
358
|
+
async def on_connect_check_health(self, check_health: bool = True) -> None:
|
|
353
359
|
self._parser.on_connect(self)
|
|
354
360
|
parser = self._parser
|
|
355
361
|
|
|
@@ -407,7 +413,7 @@ class AbstractConnection:
|
|
|
407
413
|
# update cluster exception classes
|
|
408
414
|
self._parser.EXCEPTION_CLASSES = parser.EXCEPTION_CLASSES
|
|
409
415
|
self._parser.on_connect(self)
|
|
410
|
-
await self.send_command("HELLO", self.protocol)
|
|
416
|
+
await self.send_command("HELLO", self.protocol, check_health=check_health)
|
|
411
417
|
response = await self.read_response()
|
|
412
418
|
# if response.get(b"proto") != self.protocol and response.get(
|
|
413
419
|
# "proto"
|
|
@@ -416,18 +422,35 @@ class AbstractConnection:
|
|
|
416
422
|
|
|
417
423
|
# if a client_name is given, set it
|
|
418
424
|
if self.client_name:
|
|
419
|
-
await self.send_command(
|
|
425
|
+
await self.send_command(
|
|
426
|
+
"CLIENT",
|
|
427
|
+
"SETNAME",
|
|
428
|
+
self.client_name,
|
|
429
|
+
check_health=check_health,
|
|
430
|
+
)
|
|
420
431
|
if str_if_bytes(await self.read_response()) != "OK":
|
|
421
432
|
raise ConnectionError("Error setting client name")
|
|
422
433
|
|
|
423
434
|
# set the library name and version, pipeline for lower startup latency
|
|
424
435
|
if self.lib_name:
|
|
425
|
-
await self.send_command(
|
|
436
|
+
await self.send_command(
|
|
437
|
+
"CLIENT",
|
|
438
|
+
"SETINFO",
|
|
439
|
+
"LIB-NAME",
|
|
440
|
+
self.lib_name,
|
|
441
|
+
check_health=check_health,
|
|
442
|
+
)
|
|
426
443
|
if self.lib_version:
|
|
427
|
-
await self.send_command(
|
|
444
|
+
await self.send_command(
|
|
445
|
+
"CLIENT",
|
|
446
|
+
"SETINFO",
|
|
447
|
+
"LIB-VER",
|
|
448
|
+
self.lib_version,
|
|
449
|
+
check_health=check_health,
|
|
450
|
+
)
|
|
428
451
|
# if a database is specified, switch to it. Also pipeline this
|
|
429
452
|
if self.db:
|
|
430
|
-
await self.send_command("SELECT", self.db)
|
|
453
|
+
await self.send_command("SELECT", self.db, check_health=check_health)
|
|
431
454
|
|
|
432
455
|
# read responses from pipeline
|
|
433
456
|
for _ in (sent for sent in (self.lib_name, self.lib_version) if sent):
|
|
@@ -489,8 +512,8 @@ class AbstractConnection:
|
|
|
489
512
|
self, command: Union[bytes, str, Iterable[bytes]], check_health: bool = True
|
|
490
513
|
) -> None:
|
|
491
514
|
if not self.is_connected:
|
|
492
|
-
await self.
|
|
493
|
-
|
|
515
|
+
await self.connect_check_health(check_health=False)
|
|
516
|
+
if check_health:
|
|
494
517
|
await self.check_health()
|
|
495
518
|
|
|
496
519
|
try:
|
|
@@ -768,7 +791,7 @@ class SSLConnection(Connection):
|
|
|
768
791
|
self,
|
|
769
792
|
ssl_keyfile: Optional[str] = None,
|
|
770
793
|
ssl_certfile: Optional[str] = None,
|
|
771
|
-
ssl_cert_reqs: str = "required",
|
|
794
|
+
ssl_cert_reqs: Union[str, ssl.VerifyMode] = "required",
|
|
772
795
|
ssl_ca_certs: Optional[str] = None,
|
|
773
796
|
ssl_ca_data: Optional[str] = None,
|
|
774
797
|
ssl_check_hostname: bool = False,
|
|
@@ -842,7 +865,7 @@ class RedisSSLContext:
|
|
|
842
865
|
self,
|
|
843
866
|
keyfile: Optional[str] = None,
|
|
844
867
|
certfile: Optional[str] = None,
|
|
845
|
-
cert_reqs: Optional[str] = None,
|
|
868
|
+
cert_reqs: Optional[Union[str, ssl.VerifyMode]] = None,
|
|
846
869
|
ca_certs: Optional[str] = None,
|
|
847
870
|
ca_data: Optional[str] = None,
|
|
848
871
|
check_hostname: bool = False,
|
|
@@ -855,7 +878,7 @@ class RedisSSLContext:
|
|
|
855
878
|
self.keyfile = keyfile
|
|
856
879
|
self.certfile = certfile
|
|
857
880
|
if cert_reqs is None:
|
|
858
|
-
|
|
881
|
+
cert_reqs = ssl.CERT_NONE
|
|
859
882
|
elif isinstance(cert_reqs, str):
|
|
860
883
|
CERT_REQS = { # noqa: N806
|
|
861
884
|
"none": ssl.CERT_NONE,
|
|
@@ -866,7 +889,8 @@ class RedisSSLContext:
|
|
|
866
889
|
raise RedisError(
|
|
867
890
|
f"Invalid SSL Certificate Requirements Flag: {cert_reqs}"
|
|
868
891
|
)
|
|
869
|
-
|
|
892
|
+
cert_reqs = CERT_REQS[cert_reqs]
|
|
893
|
+
self.cert_reqs = cert_reqs
|
|
870
894
|
self.ca_certs = ca_certs
|
|
871
895
|
self.ca_data = ca_data
|
|
872
896
|
self.check_hostname = check_hostname
|
redis/asyncio/sentinel.py
CHANGED
|
@@ -326,6 +326,8 @@ class Sentinel(AsyncSentinelCommands):
|
|
|
326
326
|
):
|
|
327
327
|
"""
|
|
328
328
|
Returns a redis client instance for the ``service_name`` master.
|
|
329
|
+
Sentinel client will detect failover and reconnect Redis clients
|
|
330
|
+
automatically.
|
|
329
331
|
|
|
330
332
|
A :py:class:`~redis.sentinel.SentinelConnectionPool` class is
|
|
331
333
|
used to retrieve the master's address before establishing a new
|
redis/client.py
CHANGED
|
@@ -61,6 +61,7 @@ from redis.utils import (
|
|
|
61
61
|
get_lib_version,
|
|
62
62
|
safe_str,
|
|
63
63
|
str_if_bytes,
|
|
64
|
+
truncate_text,
|
|
64
65
|
)
|
|
65
66
|
|
|
66
67
|
if TYPE_CHECKING:
|
|
@@ -210,7 +211,7 @@ class Redis(RedisModuleCommands, CoreCommands, SentinelCommands):
|
|
|
210
211
|
ssl: bool = False,
|
|
211
212
|
ssl_keyfile: Optional[str] = None,
|
|
212
213
|
ssl_certfile: Optional[str] = None,
|
|
213
|
-
ssl_cert_reqs: str = "required",
|
|
214
|
+
ssl_cert_reqs: Union[str, "ssl.VerifyMode"] = "required",
|
|
214
215
|
ssl_ca_certs: Optional[str] = None,
|
|
215
216
|
ssl_ca_path: Optional[str] = None,
|
|
216
217
|
ssl_ca_data: Optional[str] = None,
|
|
@@ -1524,7 +1525,8 @@ class Pipeline(Redis):
|
|
|
1524
1525
|
def annotate_exception(self, exception, number, command):
|
|
1525
1526
|
cmd = " ".join(map(safe_str, command))
|
|
1526
1527
|
msg = (
|
|
1527
|
-
f"Command # {number} ({cmd}) of pipeline
|
|
1528
|
+
f"Command # {number} ({truncate_text(cmd)}) of pipeline "
|
|
1529
|
+
f"caused error: {exception.args[0]}"
|
|
1528
1530
|
)
|
|
1529
1531
|
exception.args = (msg,) + exception.args[1:]
|
|
1530
1532
|
|
redis/cluster.py
CHANGED
|
@@ -47,6 +47,7 @@ from redis.utils import (
|
|
|
47
47
|
merge_result,
|
|
48
48
|
safe_str,
|
|
49
49
|
str_if_bytes,
|
|
50
|
+
truncate_text,
|
|
50
51
|
)
|
|
51
52
|
|
|
52
53
|
|
|
@@ -2125,7 +2126,8 @@ class ClusterPipeline(RedisCluster):
|
|
|
2125
2126
|
"""
|
|
2126
2127
|
cmd = " ".join(map(safe_str, command))
|
|
2127
2128
|
msg = (
|
|
2128
|
-
f"Command # {number} ({cmd}) of pipeline
|
|
2129
|
+
f"Command # {number} ({truncate_text(cmd)}) of pipeline "
|
|
2130
|
+
f"caused error: {exception.args[0]}"
|
|
2129
2131
|
)
|
|
2130
2132
|
exception.args = (msg,) + exception.args[1:]
|
|
2131
2133
|
|
redis/commands/core.py
CHANGED
|
@@ -6556,7 +6556,7 @@ class FunctionCommands:
|
|
|
6556
6556
|
This is a read-only variant of the FCALL command that cannot
|
|
6557
6557
|
execute commands that modify data.
|
|
6558
6558
|
|
|
6559
|
-
For more information see https://redis.io/commands/
|
|
6559
|
+
For more information see https://redis.io/commands/fcall_ro
|
|
6560
6560
|
"""
|
|
6561
6561
|
return self._fcall("FCALL_RO", function, numkeys, *keys_and_args)
|
|
6562
6562
|
|
redis/commands/redismodules.py
CHANGED
|
@@ -72,6 +72,14 @@ class RedisModuleCommands:
|
|
|
72
72
|
tdigest = TDigestBloom(client=self)
|
|
73
73
|
return tdigest
|
|
74
74
|
|
|
75
|
+
def vset(self):
|
|
76
|
+
"""Access the VectorSet commands namespace."""
|
|
77
|
+
|
|
78
|
+
from .vectorset import VectorSet
|
|
79
|
+
|
|
80
|
+
vset = VectorSet(client=self)
|
|
81
|
+
return vset
|
|
82
|
+
|
|
75
83
|
|
|
76
84
|
class AsyncRedisModuleCommands(RedisModuleCommands):
|
|
77
85
|
def ft(self, index_name="idx"):
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from redis._parsers.helpers import pairs_to_dict
|
|
4
|
+
from redis.commands.vectorset.utils import (
|
|
5
|
+
parse_vemb_result,
|
|
6
|
+
parse_vlinks_result,
|
|
7
|
+
parse_vsim_result,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
from ..helpers import get_protocol_version
|
|
11
|
+
from .commands import (
|
|
12
|
+
VEMB_CMD,
|
|
13
|
+
VGETATTR_CMD,
|
|
14
|
+
VINFO_CMD,
|
|
15
|
+
VLINKS_CMD,
|
|
16
|
+
VSIM_CMD,
|
|
17
|
+
VectorSetCommands,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class VectorSet(VectorSetCommands):
|
|
22
|
+
def __init__(self, client, **kwargs):
|
|
23
|
+
"""Create a new VectorSet client."""
|
|
24
|
+
# Set the module commands' callbacks
|
|
25
|
+
self._MODULE_CALLBACKS = {
|
|
26
|
+
VEMB_CMD: parse_vemb_result,
|
|
27
|
+
VGETATTR_CMD: lambda r: r and json.loads(r) or None,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
self._RESP2_MODULE_CALLBACKS = {
|
|
31
|
+
VINFO_CMD: lambda r: r and pairs_to_dict(r) or None,
|
|
32
|
+
VSIM_CMD: parse_vsim_result,
|
|
33
|
+
VLINKS_CMD: parse_vlinks_result,
|
|
34
|
+
}
|
|
35
|
+
self._RESP3_MODULE_CALLBACKS = {}
|
|
36
|
+
|
|
37
|
+
self.client = client
|
|
38
|
+
self.execute_command = client.execute_command
|
|
39
|
+
|
|
40
|
+
if get_protocol_version(self.client) in ["3", 3]:
|
|
41
|
+
self._MODULE_CALLBACKS.update(self._RESP3_MODULE_CALLBACKS)
|
|
42
|
+
else:
|
|
43
|
+
self._MODULE_CALLBACKS.update(self._RESP2_MODULE_CALLBACKS)
|
|
44
|
+
|
|
45
|
+
for k, v in self._MODULE_CALLBACKS.items():
|
|
46
|
+
self.client.set_response_callback(k, v)
|
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from enum import Enum
|
|
3
|
+
from typing import Awaitable, Dict, List, Optional, Union
|
|
4
|
+
|
|
5
|
+
from redis.client import NEVER_DECODE
|
|
6
|
+
from redis.commands.helpers import get_protocol_version
|
|
7
|
+
from redis.exceptions import DataError
|
|
8
|
+
from redis.typing import CommandsProtocol, EncodableT, KeyT, Number
|
|
9
|
+
|
|
10
|
+
VADD_CMD = "VADD"
|
|
11
|
+
VSIM_CMD = "VSIM"
|
|
12
|
+
VREM_CMD = "VREM"
|
|
13
|
+
VDIM_CMD = "VDIM"
|
|
14
|
+
VCARD_CMD = "VCARD"
|
|
15
|
+
VEMB_CMD = "VEMB"
|
|
16
|
+
VLINKS_CMD = "VLINKS"
|
|
17
|
+
VINFO_CMD = "VINFO"
|
|
18
|
+
VSETATTR_CMD = "VSETATTR"
|
|
19
|
+
VGETATTR_CMD = "VGETATTR"
|
|
20
|
+
VRANDMEMBER_CMD = "VRANDMEMBER"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class QuantizationOptions(Enum):
|
|
24
|
+
"""Quantization options for the VADD command."""
|
|
25
|
+
|
|
26
|
+
NOQUANT = "NOQUANT"
|
|
27
|
+
BIN = "BIN"
|
|
28
|
+
Q8 = "Q8"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CallbacksOptions(Enum):
|
|
32
|
+
"""Options that can be set for the commands callbacks"""
|
|
33
|
+
|
|
34
|
+
RAW = "RAW"
|
|
35
|
+
WITHSCORES = "WITHSCORES"
|
|
36
|
+
ALLOW_DECODING = "ALLOW_DECODING"
|
|
37
|
+
RESP3 = "RESP3"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class VectorSetCommands(CommandsProtocol):
|
|
41
|
+
"""Redis VectorSet commands"""
|
|
42
|
+
|
|
43
|
+
def vadd(
|
|
44
|
+
self,
|
|
45
|
+
key: KeyT,
|
|
46
|
+
vector: Union[List[float], bytes],
|
|
47
|
+
element: str,
|
|
48
|
+
reduce_dim: Optional[int] = None,
|
|
49
|
+
cas: Optional[bool] = False,
|
|
50
|
+
quantization: Optional[QuantizationOptions] = None,
|
|
51
|
+
ef: Optional[Number] = None,
|
|
52
|
+
attributes: Optional[Union[dict, str]] = None,
|
|
53
|
+
numlinks: Optional[int] = None,
|
|
54
|
+
) -> Union[Awaitable[int], int]:
|
|
55
|
+
"""
|
|
56
|
+
Add vector ``vector`` for element ``element`` to a vector set ``key``.
|
|
57
|
+
|
|
58
|
+
``reduce_dim`` sets the dimensions to reduce the vector to.
|
|
59
|
+
If not provided, the vector is not reduced.
|
|
60
|
+
|
|
61
|
+
``cas`` is a boolean flag that indicates whether to use CAS (check-and-set style)
|
|
62
|
+
when adding the vector. If not provided, CAS is not used.
|
|
63
|
+
|
|
64
|
+
``quantization`` sets the quantization type to use.
|
|
65
|
+
If not provided, int8 quantization is used.
|
|
66
|
+
The options are:
|
|
67
|
+
- NOQUANT: No quantization
|
|
68
|
+
- BIN: Binary quantization
|
|
69
|
+
- Q8: Signed 8-bit quantization
|
|
70
|
+
|
|
71
|
+
``ef`` sets the exploration factor to use.
|
|
72
|
+
If not provided, the default exploration factor is used.
|
|
73
|
+
|
|
74
|
+
``attributes`` is a dictionary or json string that contains the attributes to set for the vector.
|
|
75
|
+
If not provided, no attributes are set.
|
|
76
|
+
|
|
77
|
+
``numlinks`` sets the number of links to create for the vector.
|
|
78
|
+
If not provided, the default number of links is used.
|
|
79
|
+
|
|
80
|
+
For more information see https://redis.io/commands/vadd
|
|
81
|
+
"""
|
|
82
|
+
if not vector or not element:
|
|
83
|
+
raise DataError("Both vector and element must be provided")
|
|
84
|
+
|
|
85
|
+
pieces = []
|
|
86
|
+
if reduce_dim:
|
|
87
|
+
pieces.extend(["REDUCE", reduce_dim])
|
|
88
|
+
|
|
89
|
+
values_pieces = []
|
|
90
|
+
if isinstance(vector, bytes):
|
|
91
|
+
values_pieces.extend(["FP32", vector])
|
|
92
|
+
else:
|
|
93
|
+
values_pieces.extend(["VALUES", len(vector)])
|
|
94
|
+
values_pieces.extend(vector)
|
|
95
|
+
pieces.extend(values_pieces)
|
|
96
|
+
|
|
97
|
+
pieces.append(element)
|
|
98
|
+
|
|
99
|
+
if cas:
|
|
100
|
+
pieces.append("CAS")
|
|
101
|
+
|
|
102
|
+
if quantization:
|
|
103
|
+
pieces.append(quantization.value)
|
|
104
|
+
|
|
105
|
+
if ef:
|
|
106
|
+
pieces.extend(["EF", ef])
|
|
107
|
+
|
|
108
|
+
if attributes:
|
|
109
|
+
if isinstance(attributes, dict):
|
|
110
|
+
# transform attributes to json string
|
|
111
|
+
attributes_json = json.dumps(attributes)
|
|
112
|
+
else:
|
|
113
|
+
attributes_json = attributes
|
|
114
|
+
pieces.extend(["SETATTR", attributes_json])
|
|
115
|
+
|
|
116
|
+
if numlinks:
|
|
117
|
+
pieces.extend(["M", numlinks])
|
|
118
|
+
|
|
119
|
+
return self.execute_command(VADD_CMD, key, *pieces)
|
|
120
|
+
|
|
121
|
+
def vsim(
|
|
122
|
+
self,
|
|
123
|
+
key: KeyT,
|
|
124
|
+
input: Union[List[float], bytes, str],
|
|
125
|
+
with_scores: Optional[bool] = False,
|
|
126
|
+
count: Optional[int] = None,
|
|
127
|
+
ef: Optional[Number] = None,
|
|
128
|
+
filter: Optional[str] = None,
|
|
129
|
+
filter_ef: Optional[str] = None,
|
|
130
|
+
truth: Optional[bool] = False,
|
|
131
|
+
no_thread: Optional[bool] = False,
|
|
132
|
+
) -> Union[
|
|
133
|
+
Awaitable[Optional[List[Union[List[EncodableT], Dict[EncodableT, Number]]]]],
|
|
134
|
+
Optional[List[Union[List[EncodableT], Dict[EncodableT, Number]]]],
|
|
135
|
+
]:
|
|
136
|
+
"""
|
|
137
|
+
Compare a vector or element ``input`` with the other vectors in a vector set ``key``.
|
|
138
|
+
|
|
139
|
+
``with_scores`` sets if the results should be returned with the
|
|
140
|
+
similarity scores of the elements in the result.
|
|
141
|
+
|
|
142
|
+
``count`` sets the number of results to return.
|
|
143
|
+
|
|
144
|
+
``ef`` sets the exploration factor.
|
|
145
|
+
|
|
146
|
+
``filter`` sets filter that should be applied for the search.
|
|
147
|
+
|
|
148
|
+
``filter_ef`` sets the max filtering effort.
|
|
149
|
+
|
|
150
|
+
``truth`` when enabled forces the command to perform linear scan.
|
|
151
|
+
|
|
152
|
+
``no_thread`` when enabled forces the command to execute the search
|
|
153
|
+
on the data structure in the main thread.
|
|
154
|
+
|
|
155
|
+
For more information see https://redis.io/commands/vsim
|
|
156
|
+
"""
|
|
157
|
+
|
|
158
|
+
if not input:
|
|
159
|
+
raise DataError("'input' should be provided")
|
|
160
|
+
|
|
161
|
+
pieces = []
|
|
162
|
+
options = {}
|
|
163
|
+
|
|
164
|
+
if isinstance(input, bytes):
|
|
165
|
+
pieces.extend(["FP32", input])
|
|
166
|
+
elif isinstance(input, list):
|
|
167
|
+
pieces.extend(["VALUES", len(input)])
|
|
168
|
+
pieces.extend(input)
|
|
169
|
+
else:
|
|
170
|
+
pieces.extend(["ELE", input])
|
|
171
|
+
|
|
172
|
+
if with_scores:
|
|
173
|
+
pieces.append("WITHSCORES")
|
|
174
|
+
options[CallbacksOptions.WITHSCORES.value] = True
|
|
175
|
+
|
|
176
|
+
if count:
|
|
177
|
+
pieces.extend(["COUNT", count])
|
|
178
|
+
|
|
179
|
+
if ef:
|
|
180
|
+
pieces.extend(["EF", ef])
|
|
181
|
+
|
|
182
|
+
if filter:
|
|
183
|
+
pieces.extend(["FILTER", filter])
|
|
184
|
+
|
|
185
|
+
if filter_ef:
|
|
186
|
+
pieces.extend(["FILTER-EF", filter_ef])
|
|
187
|
+
|
|
188
|
+
if truth:
|
|
189
|
+
pieces.append("TRUTH")
|
|
190
|
+
|
|
191
|
+
if no_thread:
|
|
192
|
+
pieces.append("NOTHREAD")
|
|
193
|
+
|
|
194
|
+
return self.execute_command(VSIM_CMD, key, *pieces, **options)
|
|
195
|
+
|
|
196
|
+
def vdim(self, key: KeyT) -> Union[Awaitable[int], int]:
|
|
197
|
+
"""
|
|
198
|
+
Get the dimension of a vector set.
|
|
199
|
+
|
|
200
|
+
In the case of vectors that were populated using the `REDUCE`
|
|
201
|
+
option, for random projection, the vector set will report the size of
|
|
202
|
+
the projected (reduced) dimension.
|
|
203
|
+
|
|
204
|
+
Raises `redis.exceptions.ResponseError` if the vector set doesn't exist.
|
|
205
|
+
|
|
206
|
+
For more information see https://redis.io/commands/vdim
|
|
207
|
+
"""
|
|
208
|
+
return self.execute_command(VDIM_CMD, key)
|
|
209
|
+
|
|
210
|
+
def vcard(self, key: KeyT) -> Union[Awaitable[int], int]:
|
|
211
|
+
"""
|
|
212
|
+
Get the cardinality(the number of elements) of a vector set with key ``key``.
|
|
213
|
+
|
|
214
|
+
Raises `redis.exceptions.ResponseError` if the vector set doesn't exist.
|
|
215
|
+
|
|
216
|
+
For more information see https://redis.io/commands/vcard
|
|
217
|
+
"""
|
|
218
|
+
return self.execute_command(VCARD_CMD, key)
|
|
219
|
+
|
|
220
|
+
def vrem(self, key: KeyT, element: str) -> Union[Awaitable[int], int]:
|
|
221
|
+
"""
|
|
222
|
+
Remove an element from a vector set.
|
|
223
|
+
|
|
224
|
+
For more information see https://redis.io/commands/vrem
|
|
225
|
+
"""
|
|
226
|
+
return self.execute_command(VREM_CMD, key, element)
|
|
227
|
+
|
|
228
|
+
def vemb(
|
|
229
|
+
self, key: KeyT, element: str, raw: Optional[bool] = False
|
|
230
|
+
) -> Union[
|
|
231
|
+
Awaitable[Optional[Union[List[EncodableT], Dict[str, EncodableT]]]],
|
|
232
|
+
Optional[Union[List[EncodableT], Dict[str, EncodableT]]],
|
|
233
|
+
]:
|
|
234
|
+
"""
|
|
235
|
+
Get the approximated vector of an element ``element`` from vector set ``key``.
|
|
236
|
+
|
|
237
|
+
``raw`` is a boolean flag that indicates whether to return the
|
|
238
|
+
interal representation used by the vector.
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
For more information see https://redis.io/commands/vembed
|
|
242
|
+
"""
|
|
243
|
+
options = {}
|
|
244
|
+
pieces = []
|
|
245
|
+
pieces.extend([key, element])
|
|
246
|
+
|
|
247
|
+
if get_protocol_version(self.client) in ["3", 3]:
|
|
248
|
+
options[CallbacksOptions.RESP3.value] = True
|
|
249
|
+
|
|
250
|
+
if raw:
|
|
251
|
+
pieces.append("RAW")
|
|
252
|
+
|
|
253
|
+
options[NEVER_DECODE] = True
|
|
254
|
+
if (
|
|
255
|
+
hasattr(self.client, "connection_pool")
|
|
256
|
+
and self.client.connection_pool.connection_kwargs["decode_responses"]
|
|
257
|
+
) or (
|
|
258
|
+
hasattr(self.client, "nodes_manager")
|
|
259
|
+
and self.client.nodes_manager.connection_kwargs["decode_responses"]
|
|
260
|
+
):
|
|
261
|
+
# allow decoding in the postprocessing callback
|
|
262
|
+
# if the user set decode_responses=True
|
|
263
|
+
# in the connection pool
|
|
264
|
+
options[CallbacksOptions.ALLOW_DECODING.value] = True
|
|
265
|
+
|
|
266
|
+
options[CallbacksOptions.RAW.value] = True
|
|
267
|
+
|
|
268
|
+
return self.execute_command(VEMB_CMD, *pieces, **options)
|
|
269
|
+
|
|
270
|
+
def vlinks(
|
|
271
|
+
self, key: KeyT, element: str, with_scores: Optional[bool] = False
|
|
272
|
+
) -> Union[
|
|
273
|
+
Awaitable[
|
|
274
|
+
Optional[
|
|
275
|
+
List[Union[List[Union[str, bytes]], Dict[Union[str, bytes], Number]]]
|
|
276
|
+
]
|
|
277
|
+
],
|
|
278
|
+
Optional[List[Union[List[Union[str, bytes]], Dict[Union[str, bytes], Number]]]],
|
|
279
|
+
]:
|
|
280
|
+
"""
|
|
281
|
+
Returns the neighbors for each level the element ``element`` exists in the vector set ``key``.
|
|
282
|
+
|
|
283
|
+
The result is a list of lists, where each list contains the neighbors for one level.
|
|
284
|
+
If the element does not exist, or if the vector set does not exist, None is returned.
|
|
285
|
+
|
|
286
|
+
If the ``WITHSCORES`` option is provided, the result is a list of dicts,
|
|
287
|
+
where each dict contains the neighbors for one level, with the scores as values.
|
|
288
|
+
|
|
289
|
+
For more information see https://redis.io/commands/vlinks
|
|
290
|
+
"""
|
|
291
|
+
options = {}
|
|
292
|
+
pieces = []
|
|
293
|
+
pieces.extend([key, element])
|
|
294
|
+
|
|
295
|
+
if with_scores:
|
|
296
|
+
pieces.append("WITHSCORES")
|
|
297
|
+
options[CallbacksOptions.WITHSCORES.value] = True
|
|
298
|
+
|
|
299
|
+
return self.execute_command(VLINKS_CMD, *pieces, **options)
|
|
300
|
+
|
|
301
|
+
def vinfo(self, key: KeyT) -> Union[Awaitable[dict], dict]:
|
|
302
|
+
"""
|
|
303
|
+
Get information about a vector set.
|
|
304
|
+
|
|
305
|
+
For more information see https://redis.io/commands/vinfo
|
|
306
|
+
"""
|
|
307
|
+
return self.execute_command(VINFO_CMD, key)
|
|
308
|
+
|
|
309
|
+
def vsetattr(
|
|
310
|
+
self, key: KeyT, element: str, attributes: Optional[Union[dict, str]] = None
|
|
311
|
+
) -> Union[Awaitable[int], int]:
|
|
312
|
+
"""
|
|
313
|
+
Associate or remove JSON attributes ``attributes`` of element ``element``
|
|
314
|
+
for vector set ``key``.
|
|
315
|
+
|
|
316
|
+
For more information see https://redis.io/commands/vsetattr
|
|
317
|
+
"""
|
|
318
|
+
if attributes is None:
|
|
319
|
+
attributes_json = "{}"
|
|
320
|
+
elif isinstance(attributes, dict):
|
|
321
|
+
# transform attributes to json string
|
|
322
|
+
attributes_json = json.dumps(attributes)
|
|
323
|
+
else:
|
|
324
|
+
attributes_json = attributes
|
|
325
|
+
|
|
326
|
+
return self.execute_command(VSETATTR_CMD, key, element, attributes_json)
|
|
327
|
+
|
|
328
|
+
def vgetattr(
|
|
329
|
+
self, key: KeyT, element: str
|
|
330
|
+
) -> Union[Optional[Awaitable[dict]], Optional[dict]]:
|
|
331
|
+
"""
|
|
332
|
+
Retrieve the JSON attributes of an element ``elemet`` for vector set ``key``.
|
|
333
|
+
|
|
334
|
+
If the element does not exist, or if the vector set does not exist, None is
|
|
335
|
+
returned.
|
|
336
|
+
|
|
337
|
+
For more information see https://redis.io/commands/vgetattr
|
|
338
|
+
"""
|
|
339
|
+
return self.execute_command(VGETATTR_CMD, key, element)
|
|
340
|
+
|
|
341
|
+
def vrandmember(
|
|
342
|
+
self, key: KeyT, count: Optional[int] = None
|
|
343
|
+
) -> Union[
|
|
344
|
+
Awaitable[Optional[Union[List[str], str]]], Optional[Union[List[str], str]]
|
|
345
|
+
]:
|
|
346
|
+
"""
|
|
347
|
+
Returns random elements from a vector set ``key``.
|
|
348
|
+
|
|
349
|
+
``count`` is the number of elements to return.
|
|
350
|
+
If ``count`` is not provided, a single element is returned as a single string.
|
|
351
|
+
If ``count`` is positive(smaller than the number of elements
|
|
352
|
+
in the vector set), the command returns a list with up to ``count``
|
|
353
|
+
distinct elements from the vector set
|
|
354
|
+
If ``count`` is negative, the command returns a list with ``count`` random elements,
|
|
355
|
+
potentially with duplicates.
|
|
356
|
+
If ``count`` is greater than the number of elements in the vector set,
|
|
357
|
+
only the entire set is returned as a list.
|
|
358
|
+
|
|
359
|
+
If the vector set does not exist, ``None`` is returned.
|
|
360
|
+
|
|
361
|
+
For more information see https://redis.io/commands/vrandmember
|
|
362
|
+
"""
|
|
363
|
+
pieces = []
|
|
364
|
+
pieces.append(key)
|
|
365
|
+
if count is not None:
|
|
366
|
+
pieces.append(count)
|
|
367
|
+
return self.execute_command(VRANDMEMBER_CMD, *pieces)
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
from redis._parsers.helpers import pairs_to_dict
|
|
2
|
+
from redis.commands.vectorset.commands import CallbacksOptions
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def parse_vemb_result(response, **options):
|
|
6
|
+
"""
|
|
7
|
+
Handle VEMB result since the command can returning different result
|
|
8
|
+
structures depending on input options and on quantization type of the vector set.
|
|
9
|
+
|
|
10
|
+
Parsing VEMB result into:
|
|
11
|
+
- List[Union[bytes, Union[int, float]]]
|
|
12
|
+
- Dict[str, Union[bytes, str, float]]
|
|
13
|
+
"""
|
|
14
|
+
if response is None:
|
|
15
|
+
return response
|
|
16
|
+
|
|
17
|
+
if options.get(CallbacksOptions.RAW.value):
|
|
18
|
+
result = {}
|
|
19
|
+
result["quantization"] = (
|
|
20
|
+
response[0].decode("utf-8")
|
|
21
|
+
if options.get(CallbacksOptions.ALLOW_DECODING.value)
|
|
22
|
+
else response[0]
|
|
23
|
+
)
|
|
24
|
+
result["raw"] = response[1]
|
|
25
|
+
result["l2"] = float(response[2])
|
|
26
|
+
if len(response) > 3:
|
|
27
|
+
result["range"] = float(response[3])
|
|
28
|
+
return result
|
|
29
|
+
else:
|
|
30
|
+
if options.get(CallbacksOptions.RESP3.value):
|
|
31
|
+
return response
|
|
32
|
+
|
|
33
|
+
result = []
|
|
34
|
+
for i in range(len(response)):
|
|
35
|
+
try:
|
|
36
|
+
result.append(int(response[i]))
|
|
37
|
+
except ValueError:
|
|
38
|
+
# if the value is not an integer, it should be a float
|
|
39
|
+
result.append(float(response[i]))
|
|
40
|
+
|
|
41
|
+
return result
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def parse_vlinks_result(response, **options):
|
|
45
|
+
"""
|
|
46
|
+
Handle VLINKS result since the command can be returning different result
|
|
47
|
+
structures depending on input options.
|
|
48
|
+
Parsing VLINKS result into:
|
|
49
|
+
- List[List[str]]
|
|
50
|
+
- List[Dict[str, Number]]
|
|
51
|
+
"""
|
|
52
|
+
if response is None:
|
|
53
|
+
return response
|
|
54
|
+
|
|
55
|
+
if options.get(CallbacksOptions.WITHSCORES.value):
|
|
56
|
+
result = []
|
|
57
|
+
# Redis will return a list of list of strings.
|
|
58
|
+
# This list have to be transformed to list of dicts
|
|
59
|
+
for level_item in response:
|
|
60
|
+
level_data_dict = {}
|
|
61
|
+
for key, value in pairs_to_dict(level_item).items():
|
|
62
|
+
value = float(value)
|
|
63
|
+
level_data_dict[key] = value
|
|
64
|
+
result.append(level_data_dict)
|
|
65
|
+
return result
|
|
66
|
+
else:
|
|
67
|
+
# return the list of elements for each level
|
|
68
|
+
# list of lists
|
|
69
|
+
return response
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def parse_vsim_result(response, **options):
|
|
73
|
+
"""
|
|
74
|
+
Handle VSIM result since the command can be returning different result
|
|
75
|
+
structures depending on input options.
|
|
76
|
+
Parsing VSIM result into:
|
|
77
|
+
- List[List[str]]
|
|
78
|
+
- List[Dict[str, Number]]
|
|
79
|
+
"""
|
|
80
|
+
if response is None:
|
|
81
|
+
return response
|
|
82
|
+
|
|
83
|
+
if options.get(CallbacksOptions.WITHSCORES.value):
|
|
84
|
+
# Redis will return a list of list of pairs.
|
|
85
|
+
# This list have to be transformed to dict
|
|
86
|
+
result_dict = {}
|
|
87
|
+
for key, value in pairs_to_dict(response).items():
|
|
88
|
+
value = float(value)
|
|
89
|
+
result_dict[key] = value
|
|
90
|
+
return result_dict
|
|
91
|
+
else:
|
|
92
|
+
# return the list of elements for each level
|
|
93
|
+
# list of lists
|
|
94
|
+
return response
|
redis/connection.py
CHANGED
|
@@ -376,6 +376,9 @@ class AbstractConnection(ConnectionInterface):
|
|
|
376
376
|
|
|
377
377
|
def connect(self):
|
|
378
378
|
"Connects to the Redis server if not already connected"
|
|
379
|
+
self.connect_check_health(check_health=True)
|
|
380
|
+
|
|
381
|
+
def connect_check_health(self, check_health: bool = True):
|
|
379
382
|
if self._sock:
|
|
380
383
|
return
|
|
381
384
|
try:
|
|
@@ -391,7 +394,7 @@ class AbstractConnection(ConnectionInterface):
|
|
|
391
394
|
try:
|
|
392
395
|
if self.redis_connect_func is None:
|
|
393
396
|
# Use the default on_connect function
|
|
394
|
-
self.
|
|
397
|
+
self.on_connect_check_health(check_health=check_health)
|
|
395
398
|
else:
|
|
396
399
|
# Use the passed function redis_connect_func
|
|
397
400
|
self.redis_connect_func(self)
|
|
@@ -421,6 +424,9 @@ class AbstractConnection(ConnectionInterface):
|
|
|
421
424
|
return format_error_message(self._host_error(), exception)
|
|
422
425
|
|
|
423
426
|
def on_connect(self):
|
|
427
|
+
self.on_connect_check_health(check_health=True)
|
|
428
|
+
|
|
429
|
+
def on_connect_check_health(self, check_health: bool = True):
|
|
424
430
|
"Initialize the connection, authenticate and select a database"
|
|
425
431
|
self._parser.on_connect(self)
|
|
426
432
|
parser = self._parser
|
|
@@ -479,7 +485,7 @@ class AbstractConnection(ConnectionInterface):
|
|
|
479
485
|
# update cluster exception classes
|
|
480
486
|
self._parser.EXCEPTION_CLASSES = parser.EXCEPTION_CLASSES
|
|
481
487
|
self._parser.on_connect(self)
|
|
482
|
-
self.send_command("HELLO", self.protocol)
|
|
488
|
+
self.send_command("HELLO", self.protocol, check_health=check_health)
|
|
483
489
|
self.handshake_metadata = self.read_response()
|
|
484
490
|
if (
|
|
485
491
|
self.handshake_metadata.get(b"proto") != self.protocol
|
|
@@ -489,28 +495,45 @@ class AbstractConnection(ConnectionInterface):
|
|
|
489
495
|
|
|
490
496
|
# if a client_name is given, set it
|
|
491
497
|
if self.client_name:
|
|
492
|
-
self.send_command(
|
|
498
|
+
self.send_command(
|
|
499
|
+
"CLIENT",
|
|
500
|
+
"SETNAME",
|
|
501
|
+
self.client_name,
|
|
502
|
+
check_health=check_health,
|
|
503
|
+
)
|
|
493
504
|
if str_if_bytes(self.read_response()) != "OK":
|
|
494
505
|
raise ConnectionError("Error setting client name")
|
|
495
506
|
|
|
496
507
|
try:
|
|
497
508
|
# set the library name and version
|
|
498
509
|
if self.lib_name:
|
|
499
|
-
self.send_command(
|
|
510
|
+
self.send_command(
|
|
511
|
+
"CLIENT",
|
|
512
|
+
"SETINFO",
|
|
513
|
+
"LIB-NAME",
|
|
514
|
+
self.lib_name,
|
|
515
|
+
check_health=check_health,
|
|
516
|
+
)
|
|
500
517
|
self.read_response()
|
|
501
518
|
except ResponseError:
|
|
502
519
|
pass
|
|
503
520
|
|
|
504
521
|
try:
|
|
505
522
|
if self.lib_version:
|
|
506
|
-
self.send_command(
|
|
523
|
+
self.send_command(
|
|
524
|
+
"CLIENT",
|
|
525
|
+
"SETINFO",
|
|
526
|
+
"LIB-VER",
|
|
527
|
+
self.lib_version,
|
|
528
|
+
check_health=check_health,
|
|
529
|
+
)
|
|
507
530
|
self.read_response()
|
|
508
531
|
except ResponseError:
|
|
509
532
|
pass
|
|
510
533
|
|
|
511
534
|
# if a database is specified, switch to it
|
|
512
535
|
if self.db:
|
|
513
|
-
self.send_command("SELECT", self.db)
|
|
536
|
+
self.send_command("SELECT", self.db, check_health=check_health)
|
|
514
537
|
if str_if_bytes(self.read_response()) != "OK":
|
|
515
538
|
raise ConnectionError("Invalid Database")
|
|
516
539
|
|
|
@@ -552,7 +575,7 @@ class AbstractConnection(ConnectionInterface):
|
|
|
552
575
|
def send_packed_command(self, command, check_health=True):
|
|
553
576
|
"""Send an already packed command to the Redis server"""
|
|
554
577
|
if not self._sock:
|
|
555
|
-
self.
|
|
578
|
+
self.connect_check_health(check_health=False)
|
|
556
579
|
# guard against health check recursion
|
|
557
580
|
if check_health:
|
|
558
581
|
self.check_health()
|
|
@@ -764,6 +787,10 @@ class Connection(AbstractConnection):
|
|
|
764
787
|
except OSError as _:
|
|
765
788
|
err = _
|
|
766
789
|
if sock is not None:
|
|
790
|
+
try:
|
|
791
|
+
sock.shutdown(socket.SHUT_RDWR) # ensure a clean close
|
|
792
|
+
except OSError:
|
|
793
|
+
pass
|
|
767
794
|
sock.close()
|
|
768
795
|
|
|
769
796
|
if err is not None:
|
|
@@ -1017,7 +1044,7 @@ class SSLConnection(Connection):
|
|
|
1017
1044
|
Args:
|
|
1018
1045
|
ssl_keyfile: Path to an ssl private key. Defaults to None.
|
|
1019
1046
|
ssl_certfile: Path to an ssl certificate. Defaults to None.
|
|
1020
|
-
ssl_cert_reqs: The string value for the SSLContext.verify_mode (none, optional, required). Defaults to "required".
|
|
1047
|
+
ssl_cert_reqs: The string value for the SSLContext.verify_mode (none, optional, required), or an ssl.VerifyMode. Defaults to "required".
|
|
1021
1048
|
ssl_ca_certs: The path to a file of concatenated CA certificates in PEM format. Defaults to None.
|
|
1022
1049
|
ssl_ca_data: Either an ASCII string of one or more PEM-encoded certificates or a bytes-like object of DER-encoded certificates.
|
|
1023
1050
|
ssl_check_hostname: If set, match the hostname during the SSL handshake. Defaults to False.
|
|
@@ -1179,6 +1206,10 @@ class UnixDomainSocketConnection(AbstractConnection):
|
|
|
1179
1206
|
sock.connect(self.path)
|
|
1180
1207
|
except OSError:
|
|
1181
1208
|
# Prevent ResourceWarnings for unclosed sockets.
|
|
1209
|
+
try:
|
|
1210
|
+
sock.shutdown(socket.SHUT_RDWR) # ensure a clean close
|
|
1211
|
+
except OSError:
|
|
1212
|
+
pass
|
|
1182
1213
|
sock.close()
|
|
1183
1214
|
raise
|
|
1184
1215
|
sock.settimeout(self.socket_timeout)
|
redis/sentinel.py
CHANGED
|
@@ -349,6 +349,8 @@ class Sentinel(SentinelCommands):
|
|
|
349
349
|
):
|
|
350
350
|
"""
|
|
351
351
|
Returns a redis client instance for the ``service_name`` master.
|
|
352
|
+
Sentinel client will detect failover and reconnect Redis clients
|
|
353
|
+
automatically.
|
|
352
354
|
|
|
353
355
|
A :py:class:`~redis.sentinel.SentinelConnectionPool` class is
|
|
354
356
|
used to retrieve the master's address before establishing a new
|
redis/utils.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import logging
|
|
3
|
+
import textwrap
|
|
3
4
|
from contextlib import contextmanager
|
|
4
5
|
from functools import wraps
|
|
5
6
|
from typing import Any, Dict, List, Mapping, Optional, Union
|
|
@@ -298,3 +299,9 @@ def extract_expire_flags(
|
|
|
298
299
|
exp_options.extend(["PXAT", pxat])
|
|
299
300
|
|
|
300
301
|
return exp_options
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
def truncate_text(txt, max_length=100):
|
|
305
|
+
return textwrap.shorten(
|
|
306
|
+
text=txt, width=max_length, placeholder="...", break_long_words=True
|
|
307
|
+
)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
redis/__init__.py,sha256=
|
|
1
|
+
redis/__init__.py,sha256=HqUOIkvNCUIXIazHsZTzgQFUuEF8ThizLYnusJ69ubs,1824
|
|
2
2
|
redis/backoff.py,sha256=d22h74LEatJiFd_5o8HvFW3biFBotYOFZHddHt45ydc,3663
|
|
3
3
|
redis/cache.py,sha256=68rJDNogvNwgdgBel6zSX9QziL11qsKIMhmvQvHvznM,9549
|
|
4
|
-
redis/client.py,sha256=
|
|
5
|
-
redis/cluster.py,sha256=
|
|
6
|
-
redis/connection.py,sha256=
|
|
4
|
+
redis/client.py,sha256=36anNE36mK76rerheyoLmGiiRW91KcXkoLEnc-2oKB4,62276
|
|
5
|
+
redis/cluster.py,sha256=ABMagl5AEHbCzlesI9AdzPFnIc-Ihb_DhPskyn5UG_I,98798
|
|
6
|
+
redis/connection.py,sha256=Nx8n88ATiViym85Jlgkh2ETkShs303Wmwa8OP5KKYlI,66599
|
|
7
7
|
redis/crc.py,sha256=Z3kXFtkY2LdgefnQMud1xr4vG5UYvA9LCMqNMX1ywu4,729
|
|
8
8
|
redis/credentials.py,sha256=GOnO3-LSW34efHaIrUbS742Mw8l70mRzF6UrKiKZsMY,1828
|
|
9
9
|
redis/event.py,sha256=urOK241IdgmCQ3fq7GqXRstZ2vcXRV14bBBMdN3latk,12129
|
|
@@ -12,9 +12,9 @@ redis/lock.py,sha256=GrvPSxaOqKo7iAL2oi5ZUEPsOkxAXHVE_Tp1ejgO2fY,12760
|
|
|
12
12
|
redis/ocsp.py,sha256=teYSmKnCtk6B3jJLdNYbZN4OE0mxgspt2zUPbkIQzio,11452
|
|
13
13
|
redis/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
redis/retry.py,sha256=JiIDxeD890vgi_me8pwypO1LixwhU0Fv3A5NEay8SAY,2206
|
|
15
|
-
redis/sentinel.py,sha256=
|
|
15
|
+
redis/sentinel.py,sha256=DBphu6uNp6ZCSaVDSVC8nFhSxG93a12DnzgGWpyeh64,14757
|
|
16
16
|
redis/typing.py,sha256=k7F_3Vtsexeb7mUl6txlwrY1veGDLEhtcHe9FwIJOOo,2149
|
|
17
|
-
redis/utils.py,sha256=
|
|
17
|
+
redis/utils.py,sha256=XD4ySRo9JzLIMMpQY_q9MsxUIdEPee1d7rAq7xhmSwM,8268
|
|
18
18
|
redis/_parsers/__init__.py,sha256=qkfgV2X9iyvQAvbLdSelwgz0dCk9SGAosCvuZC9-qDc,550
|
|
19
19
|
redis/_parsers/base.py,sha256=WtCbM2CaOgHk7uxwWXA2KXDlZqfYA1EJrVX_NvdOZNk,7801
|
|
20
20
|
redis/_parsers/commands.py,sha256=pmR4hl4u93UvCmeDgePHFc6pWDr4slrKEvCsdMmtj_M,11052
|
|
@@ -25,12 +25,12 @@ redis/_parsers/resp2.py,sha256=f22kH-_ZP2iNtOn6xOe65MSy_fJpu8OEn1u_hgeeojI,4813
|
|
|
25
25
|
redis/_parsers/resp3.py,sha256=jHtL1LYJegJ_LiNTsjzIvS-kZyNR58jZ_YV4cRfwuN0,11127
|
|
26
26
|
redis/_parsers/socket.py,sha256=CKD8QW_wFSNlIZzxlbNduaGpiv0I8wBcsGuAIojDfJg,5403
|
|
27
27
|
redis/asyncio/__init__.py,sha256=uoDD8XYVi0Kj6mcufYwLDUTQXmBRx7a0bhKF9stZr7I,1489
|
|
28
|
-
redis/asyncio/client.py,sha256
|
|
29
|
-
redis/asyncio/cluster.py,sha256=
|
|
30
|
-
redis/asyncio/connection.py,sha256=
|
|
28
|
+
redis/asyncio/client.py,sha256=DHeSpWCjdMjhmq-d9-STRCIhKzjRNI4ISnwfczPs1ws,61651
|
|
29
|
+
redis/asyncio/cluster.py,sha256=Nd6yG23Alv2kXz7Ffv4Fyp989b2l4zRJeJKD9xaMRIA,66987
|
|
30
|
+
redis/asyncio/connection.py,sha256=6GFT5CJvTRTIAzYd6SxvLs6QtLcYUZhkQazwWUOjQvg,48875
|
|
31
31
|
redis/asyncio/lock.py,sha256=GxgV6EsyKpMjh74KtaOPxh4fNPuwApz6Th46qhvrAws,12801
|
|
32
32
|
redis/asyncio/retry.py,sha256=SnPPOlo5gcyIFtkC4DY7HFvmDgUaILsJ3DeHioogdB8,2219
|
|
33
|
-
redis/asyncio/sentinel.py,sha256=
|
|
33
|
+
redis/asyncio/sentinel.py,sha256=H7N_hvdATojwY06aH1AawFV-05AImqtOSAq0xKElbbk,14636
|
|
34
34
|
redis/asyncio/utils.py,sha256=31xFzXczDgSRyf6hSjiwue1eDQ_XlP_OJdp5dKxW_aE,718
|
|
35
35
|
redis/auth/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
36
36
|
redis/auth/err.py,sha256=WYkbuDIzwp1S-eAvsya6QMlO6g9QIXbzMITOsTWX0xk,694
|
|
@@ -39,9 +39,9 @@ redis/auth/token.py,sha256=qYwAgxFW3S93QDUqp1BTsj7Pj9ZohnixGeOX0s7AsjY,3317
|
|
|
39
39
|
redis/auth/token_manager.py,sha256=ShBsYXiBZBJBOMB_Y-pXfLwEOAmc9s1okaCECinNZ7g,12018
|
|
40
40
|
redis/commands/__init__.py,sha256=cTUH-MGvaLYS0WuoytyqtN1wniw2A1KbkUXcpvOSY3I,576
|
|
41
41
|
redis/commands/cluster.py,sha256=vdWdpl4mP51oqfYBZHg5CUXt6jPaNp7aCLHyTieDrt8,31248
|
|
42
|
-
redis/commands/core.py,sha256=
|
|
42
|
+
redis/commands/core.py,sha256=dw1iVparHVoWBivdRNuEQXHNwP7_8cTJ-yj1uFZeDC4,238478
|
|
43
43
|
redis/commands/helpers.py,sha256=f1BDIVgfjyE8xTyTjcVH_vffaNTSd6r1ScN_CcJFbmk,2950
|
|
44
|
-
redis/commands/redismodules.py,sha256=
|
|
44
|
+
redis/commands/redismodules.py,sha256=H3lHeyWO-KrOmNn0dilebQyiHRCflED4tgXuswWpSGs,2143
|
|
45
45
|
redis/commands/sentinel.py,sha256=hRcIQ9x9nEkdcCsJzo6Ves6vk-3tsfQqfJTT_v3oLY0,4110
|
|
46
46
|
redis/commands/bf/__init__.py,sha256=qk4DA9KsMiP4WYqYeP1T5ScBwctsVtlLyMhrYIyq1Zc,8019
|
|
47
47
|
redis/commands/bf/commands.py,sha256=xeKt8E7G8HB-l922J0DLg07CEIZTVNGx_2Lfyw1gIck,21283
|
|
@@ -69,7 +69,10 @@ redis/commands/timeseries/__init__.py,sha256=gkz6wshEzzQQryBOnrAqqQzttS-AHfXmuN_
|
|
|
69
69
|
redis/commands/timeseries/commands.py,sha256=8Z2BEyP23qTYCJR_e9zdG11yWmIDwGBMO2PJNLtK2BA,47147
|
|
70
70
|
redis/commands/timeseries/info.py,sha256=meZYdu7IV9KaUWMKZs9qW4vo3Q9MwhdY-EBtKQzls5o,3223
|
|
71
71
|
redis/commands/timeseries/utils.py,sha256=NLwSOS5Dz9N8dYQSzEyBIvrItOWwfQ0xgDj8un6x3dU,1319
|
|
72
|
-
redis
|
|
73
|
-
redis
|
|
74
|
-
redis
|
|
75
|
-
redis-6.0.
|
|
72
|
+
redis/commands/vectorset/__init__.py,sha256=_fM0UdYjuzs8YWIUjQGH9QX5FwI0So8_D-5ALWWrWFc,1322
|
|
73
|
+
redis/commands/vectorset/commands.py,sha256=7CvQNFvkXuG3XPhHJ82y_oBYJwewRFz84aEi3OCH4Rw,12495
|
|
74
|
+
redis/commands/vectorset/utils.py,sha256=N-x0URyg76XC39CNfBym6FkFCVgm5NthzWKBnc2H0Xc,2981
|
|
75
|
+
redis-6.0.0b2.dist-info/METADATA,sha256=-hyuUYoQOMznsT5hBjAPVD60AIAwWL6nCiJp1yWBgmE,10510
|
|
76
|
+
redis-6.0.0b2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
77
|
+
redis-6.0.0b2.dist-info/licenses/LICENSE,sha256=pXslClvwPXr-VbdAYzE_Ktt7ANVGwKsUmok5gzP-PMg,1074
|
|
78
|
+
redis-6.0.0b2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|