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 CHANGED
@@ -42,7 +42,7 @@ def int_or_str(value):
42
42
  return value
43
43
 
44
44
 
45
- __version__ = "6.0.0b1"
45
+ __version__ = "6.0.0b2"
46
46
  VERSION = tuple(map(int_or_str, __version__.split(".")))
47
47
 
48
48
 
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 = f"Command # {number} ({cmd}) of pipeline caused error: {exception.args}"
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} ({command}) of pipeline "
1637
- f"caused error: {result.args}"
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
@@ -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.on_connect()
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("CLIENT", "SETNAME", self.client_name)
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("CLIENT", "SETINFO", "LIB-NAME", self.lib_name)
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("CLIENT", "SETINFO", "LIB-VER", self.lib_version)
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.connect()
493
- elif check_health:
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
- self.cert_reqs = ssl.CERT_NONE
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
- self.cert_reqs = CERT_REQS[cert_reqs]
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 caused error: {exception.args[0]}"
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 caused error: {exception.args[0]}"
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/fcal_ro
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
 
@@ -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.on_connect()
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("CLIENT", "SETNAME", self.client_name)
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("CLIENT", "SETINFO", "LIB-NAME", self.lib_name)
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("CLIENT", "SETINFO", "LIB-VER", self.lib_version)
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.connect()
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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: redis
3
- Version: 6.0.0b1
3
+ Version: 6.0.0b2
4
4
  Summary: Python client for Redis database and key-value store
5
5
  Project-URL: Changes, https://github.com/redis/redis-py/releases
6
6
  Project-URL: Code, https://github.com/redis/redis-py
@@ -1,9 +1,9 @@
1
- redis/__init__.py,sha256=6Q6GUD5kP-_gx4CNPPxvNEXkk9JopqQmfzDW3S4hpyA,1824
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=sFmv_c0vWpZiepzTPSbpao7aO4A-Wu0bX9bq7lAKiXA,62201
5
- redis/cluster.py,sha256=m7ucCyLYd7J3ke5vxfCy60KCogrE2D_QTVBnwFwuga8,98748
6
- redis/connection.py,sha256=lv9eC3jXSBTUwCqrrFbcQtiyz8rj3c9V4lmAPpc5Zhk,65508
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=mDIC_TKbkgRBP0JrLyBs0NkVi6-aWkBIMuqqMWgKFbk,14661
15
+ redis/sentinel.py,sha256=DBphu6uNp6ZCSaVDSVC8nFhSxG93a12DnzgGWpyeh64,14757
16
16
  redis/typing.py,sha256=k7F_3Vtsexeb7mUl6txlwrY1veGDLEhtcHe9FwIJOOo,2149
17
- redis/utils.py,sha256=i7fltyT2JDVk6zqBZFeYlOcl3tXzJNq8kcDZZ8aXesE,8098
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=-U9sPfD2TD5jaW_fKOlcVzPKzYLshOnocRrFv74dRw8,61525
29
- redis/asyncio/cluster.py,sha256=Kb6xptqeiSpal6PH3KItJpfyFLjrZJ0Y3BoClDQ4kCc,66868
30
- redis/asyncio/connection.py,sha256=UI6CQX1TQz8yW5yqa-iv96tHn7fkuOOs13sdLglsr3Y,48061
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=HS5UbVlvrGuOcvQoFBHDXvf2YkYjUnKEhkxS_RG-oRk,14540
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=Ca_OhsXzLov3NTaSjIJvRGxzYmePKbdRxowrEbdh91w,238477
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=y9gxuqY7PmUjpeKLzjQFcN_gZ2IVs36jxq7sSnvG9Nc,1966
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-6.0.0b1.dist-info/METADATA,sha256=RRAjsLblR6FuHuafiPdggI22QVT943Bq439D5zAARhk,10510
73
- redis-6.0.0b1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
74
- redis-6.0.0b1.dist-info/licenses/LICENSE,sha256=pXslClvwPXr-VbdAYzE_Ktt7ANVGwKsUmok5gzP-PMg,1074
75
- redis-6.0.0b1.dist-info/RECORD,,
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,,