redis 5.3.0b4__py3-none-any.whl → 6.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.
Files changed (51) hide show
  1. redis/__init__.py +2 -11
  2. redis/_parsers/base.py +14 -2
  3. redis/_parsers/resp3.py +2 -2
  4. redis/asyncio/client.py +103 -83
  5. redis/asyncio/cluster.py +147 -102
  6. redis/asyncio/connection.py +77 -24
  7. redis/asyncio/lock.py +26 -5
  8. redis/asyncio/retry.py +12 -0
  9. redis/asyncio/sentinel.py +11 -1
  10. redis/asyncio/utils.py +1 -1
  11. redis/auth/token.py +6 -2
  12. redis/backoff.py +15 -0
  13. redis/client.py +160 -138
  14. redis/cluster.py +211 -82
  15. redis/commands/cluster.py +1 -11
  16. redis/commands/core.py +219 -207
  17. redis/commands/helpers.py +19 -76
  18. redis/commands/json/__init__.py +1 -1
  19. redis/commands/redismodules.py +5 -17
  20. redis/commands/search/aggregation.py +3 -1
  21. redis/commands/search/commands.py +43 -16
  22. redis/commands/search/dialect.py +3 -0
  23. redis/commands/search/profile_information.py +14 -0
  24. redis/commands/search/query.py +5 -1
  25. redis/commands/timeseries/__init__.py +1 -1
  26. redis/commands/vectorset/__init__.py +46 -0
  27. redis/commands/vectorset/commands.py +367 -0
  28. redis/commands/vectorset/utils.py +94 -0
  29. redis/connection.py +89 -33
  30. redis/exceptions.py +4 -1
  31. redis/lock.py +24 -4
  32. redis/ocsp.py +2 -1
  33. redis/retry.py +12 -0
  34. redis/sentinel.py +3 -1
  35. redis/typing.py +1 -1
  36. redis/utils.py +114 -1
  37. {redis-5.3.0b4.dist-info → redis-6.0.0.dist-info}/METADATA +57 -23
  38. redis-6.0.0.dist-info/RECORD +78 -0
  39. {redis-5.3.0b4.dist-info → redis-6.0.0.dist-info}/WHEEL +1 -2
  40. redis/commands/graph/__init__.py +0 -263
  41. redis/commands/graph/commands.py +0 -313
  42. redis/commands/graph/edge.py +0 -91
  43. redis/commands/graph/exceptions.py +0 -3
  44. redis/commands/graph/execution_plan.py +0 -211
  45. redis/commands/graph/node.py +0 -88
  46. redis/commands/graph/path.py +0 -78
  47. redis/commands/graph/query_result.py +0 -588
  48. redis-5.3.0b4.dist-info/RECORD +0 -82
  49. redis-5.3.0b4.dist-info/top_level.txt +0 -1
  50. /redis/commands/search/{indexDefinition.py → index_definition.py} +0 -0
  51. {redis-5.3.0b4.dist-info → redis-6.0.0.dist-info/licenses}/LICENSE +0 -0
redis/commands/helpers.py CHANGED
@@ -43,19 +43,32 @@ def parse_to_list(response):
43
43
  """Optimistically parse the response to a list."""
44
44
  res = []
45
45
 
46
+ special_values = {"infinity", "nan", "-infinity"}
47
+
46
48
  if response is None:
47
49
  return res
48
50
 
49
51
  for item in response:
52
+ if item is None:
53
+ res.append(None)
54
+ continue
50
55
  try:
51
- res.append(int(item))
52
- except ValueError:
53
- try:
54
- res.append(float(item))
55
- except ValueError:
56
- res.append(nativestr(item))
56
+ item_str = nativestr(item)
57
57
  except TypeError:
58
58
  res.append(None)
59
+ continue
60
+
61
+ if isinstance(item_str, str) and item_str.lower() in special_values:
62
+ res.append(item_str) # Keep as string
63
+ else:
64
+ try:
65
+ res.append(int(item))
66
+ except ValueError:
67
+ try:
68
+ res.append(float(item))
69
+ except ValueError:
70
+ res.append(item_str)
71
+
59
72
  return res
60
73
 
61
74
 
@@ -79,29 +92,6 @@ def parse_list_to_dict(response):
79
92
  return res
80
93
 
81
94
 
82
- def parse_to_dict(response):
83
- if response is None:
84
- return {}
85
-
86
- res = {}
87
- for det in response:
88
- if not isinstance(det, list) or not det:
89
- continue
90
- if len(det) == 1:
91
- res[det[0]] = True
92
- elif isinstance(det[1], list):
93
- res[det[0]] = parse_list_to_dict(det[1])
94
- else:
95
- try: # try to set the attribute. may be provided without value
96
- try: # try to convert the value to float
97
- res[det[0]] = float(det[1])
98
- except (TypeError, ValueError):
99
- res[det[0]] = det[1]
100
- except IndexError:
101
- pass
102
- return res
103
-
104
-
105
95
  def random_string(length=10):
106
96
  """
107
97
  Returns a random N character long string.
@@ -111,26 +101,6 @@ def random_string(length=10):
111
101
  )
112
102
 
113
103
 
114
- def quote_string(v):
115
- """
116
- RedisGraph strings must be quoted,
117
- quote_string wraps given v with quotes incase
118
- v is a string.
119
- """
120
-
121
- if isinstance(v, bytes):
122
- v = v.decode()
123
- elif not isinstance(v, str):
124
- return v
125
- if len(v) == 0:
126
- return '""'
127
-
128
- v = v.replace("\\", "\\\\")
129
- v = v.replace('"', '\\"')
130
-
131
- return f'"{v}"'
132
-
133
-
134
104
  def decode_dict_keys(obj):
135
105
  """Decode the keys of the given dictionary with utf-8."""
136
106
  newobj = copy.copy(obj)
@@ -141,33 +111,6 @@ def decode_dict_keys(obj):
141
111
  return newobj
142
112
 
143
113
 
144
- def stringify_param_value(value):
145
- """
146
- Turn a parameter value into a string suitable for the params header of
147
- a Cypher command.
148
- You may pass any value that would be accepted by `json.dumps()`.
149
-
150
- Ways in which output differs from that of `str()`:
151
- * Strings are quoted.
152
- * None --> "null".
153
- * In dictionaries, keys are _not_ quoted.
154
-
155
- :param value: The parameter value to be turned into a string.
156
- :return: string
157
- """
158
-
159
- if isinstance(value, str):
160
- return quote_string(value)
161
- elif value is None:
162
- return "null"
163
- elif isinstance(value, (list, tuple)):
164
- return f'[{",".join(map(stringify_param_value, value))}]'
165
- elif isinstance(value, dict):
166
- return f'{{{",".join(f"{k}:{stringify_param_value(v)}" for k, v in value.items())}}}' # noqa
167
- else:
168
- return str(value)
169
-
170
-
171
114
  def get_protocol_version(client):
172
115
  if isinstance(client, redis.Redis) or isinstance(client, redis.asyncio.Redis):
173
116
  return client.connection_pool.connection_kwargs.get("protocol")
@@ -120,7 +120,7 @@ class JSON(JSONCommands):
120
120
  startup_nodes=self.client.nodes_manager.startup_nodes,
121
121
  result_callbacks=self.client.result_callbacks,
122
122
  cluster_response_callbacks=self.client.cluster_response_callbacks,
123
- cluster_error_retry_attempts=self.client.cluster_error_retry_attempts,
123
+ cluster_error_retry_attempts=self.client.retry.get_retries(),
124
124
  read_from_replicas=self.client.read_from_replicas,
125
125
  reinitialize_steps=self.client.reinitialize_steps,
126
126
  lock=self.client._lock,
@@ -72,15 +72,13 @@ class RedisModuleCommands:
72
72
  tdigest = TDigestBloom(client=self)
73
73
  return tdigest
74
74
 
75
- def graph(self, index_name="idx"):
76
- """Access the graph namespace, providing support for
77
- redis graph data.
78
- """
75
+ def vset(self):
76
+ """Access the VectorSet commands namespace."""
79
77
 
80
- from .graph import Graph
78
+ from .vectorset import VectorSet
81
79
 
82
- g = Graph(client=self, name=index_name)
83
- return g
80
+ vset = VectorSet(client=self)
81
+ return vset
84
82
 
85
83
 
86
84
  class AsyncRedisModuleCommands(RedisModuleCommands):
@@ -91,13 +89,3 @@ class AsyncRedisModuleCommands(RedisModuleCommands):
91
89
 
92
90
  s = AsyncSearch(client=self, index_name=index_name)
93
91
  return s
94
-
95
- def graph(self, index_name="idx"):
96
- """Access the graph namespace, providing support for
97
- redis graph data.
98
- """
99
-
100
- from .graph import AsyncGraph
101
-
102
- g = AsyncGraph(client=self, name=index_name)
103
- return g
@@ -1,5 +1,7 @@
1
1
  from typing import List, Union
2
2
 
3
+ from redis.commands.search.dialect import DEFAULT_DIALECT
4
+
3
5
  FIELDNAME = object()
4
6
 
5
7
 
@@ -110,7 +112,7 @@ class AggregateRequest:
110
112
  self._with_schema = False
111
113
  self._verbatim = False
112
114
  self._cursor = []
113
- self._dialect = None
115
+ self._dialect = DEFAULT_DIALECT
114
116
  self._add_scores = False
115
117
  self._scorer = "TFIDF"
116
118
 
@@ -5,12 +5,13 @@ from typing import Dict, List, Optional, Union
5
5
  from redis.client import NEVER_DECODE, Pipeline
6
6
  from redis.utils import deprecated_function
7
7
 
8
- from ..helpers import get_protocol_version, parse_to_dict
8
+ from ..helpers import get_protocol_version
9
9
  from ._util import to_string
10
10
  from .aggregation import AggregateRequest, AggregateResult, Cursor
11
11
  from .document import Document
12
12
  from .field import Field
13
- from .indexDefinition import IndexDefinition
13
+ from .index_definition import IndexDefinition
14
+ from .profile_information import ProfileInformation
14
15
  from .query import Query
15
16
  from .result import Result
16
17
  from .suggestion import SuggestionParser
@@ -22,7 +23,6 @@ ALTER_CMD = "FT.ALTER"
22
23
  SEARCH_CMD = "FT.SEARCH"
23
24
  ADD_CMD = "FT.ADD"
24
25
  ADDHASH_CMD = "FT.ADDHASH"
25
- DROP_CMD = "FT.DROP"
26
26
  DROPINDEX_CMD = "FT.DROPINDEX"
27
27
  EXPLAIN_CMD = "FT.EXPLAIN"
28
28
  EXPLAINCLI_CMD = "FT.EXPLAINCLI"
@@ -34,7 +34,6 @@ SPELLCHECK_CMD = "FT.SPELLCHECK"
34
34
  DICT_ADD_CMD = "FT.DICTADD"
35
35
  DICT_DEL_CMD = "FT.DICTDEL"
36
36
  DICT_DUMP_CMD = "FT.DICTDUMP"
37
- GET_CMD = "FT.GET"
38
37
  MGET_CMD = "FT.MGET"
39
38
  CONFIG_CMD = "FT.CONFIG"
40
39
  TAGVALS_CMD = "FT.TAGVALS"
@@ -67,7 +66,7 @@ class SearchCommands:
67
66
 
68
67
  def _parse_results(self, cmd, res, **kwargs):
69
68
  if get_protocol_version(self.client) in ["3", 3]:
70
- return res
69
+ return ProfileInformation(res) if cmd == "FT.PROFILE" else res
71
70
  else:
72
71
  return self._RESP2_MODULE_CALLBACKS[cmd](res, **kwargs)
73
72
 
@@ -101,7 +100,7 @@ class SearchCommands:
101
100
  with_scores=query._with_scores,
102
101
  )
103
102
 
104
- return result, parse_to_dict(res[1])
103
+ return result, ProfileInformation(res[1])
105
104
 
106
105
  def _parse_spellcheck(self, res, **kwargs):
107
106
  corrections = {}
@@ -254,8 +253,18 @@ class SearchCommands:
254
253
 
255
254
  For more information see `FT.DROPINDEX <https://redis.io/commands/ft.dropindex>`_.
256
255
  """ # noqa
257
- delete_str = "DD" if delete_documents else ""
258
- return self.execute_command(DROPINDEX_CMD, self.index_name, delete_str)
256
+ args = [DROPINDEX_CMD, self.index_name]
257
+
258
+ delete_str = (
259
+ "DD"
260
+ if isinstance(delete_documents, bool) and delete_documents is True
261
+ else ""
262
+ )
263
+
264
+ if delete_str:
265
+ args.append(delete_str)
266
+
267
+ return self.execute_command(*args)
259
268
 
260
269
  def _add_document(
261
270
  self,
@@ -395,6 +404,7 @@ class SearchCommands:
395
404
  doc_id, conn=None, score=score, language=language, replace=replace
396
405
  )
397
406
 
407
+ @deprecated_function(version="2.0.0", reason="deprecated since redisearch 2.0")
398
408
  def delete_document(self, doc_id, conn=None, delete_actual_document=False):
399
409
  """
400
410
  Delete a document from index
@@ -429,6 +439,7 @@ class SearchCommands:
429
439
 
430
440
  return Document(id=id, **fields)
431
441
 
442
+ @deprecated_function(version="2.0.0", reason="deprecated since redisearch 2.0")
432
443
  def get(self, *ids):
433
444
  """
434
445
  Returns the full contents of multiple documents.
@@ -499,7 +510,7 @@ class SearchCommands:
499
510
  For more information see `FT.SEARCH <https://redis.io/commands/ft.search>`_.
500
511
  """ # noqa
501
512
  args, query = self._mk_query_args(query, query_params=query_params)
502
- st = time.time()
513
+ st = time.monotonic()
503
514
 
504
515
  options = {}
505
516
  if get_protocol_version(self.client) not in ["3", 3]:
@@ -511,7 +522,7 @@ class SearchCommands:
511
522
  return res
512
523
 
513
524
  return self._parse_results(
514
- SEARCH_CMD, res, query=query, duration=(time.time() - st) * 1000.0
525
+ SEARCH_CMD, res, query=query, duration=(time.monotonic() - st) * 1000.0
515
526
  )
516
527
 
517
528
  def explain(
@@ -585,7 +596,7 @@ class SearchCommands:
585
596
 
586
597
  def profile(
587
598
  self,
588
- query: Union[str, Query, AggregateRequest],
599
+ query: Union[Query, AggregateRequest],
589
600
  limited: bool = False,
590
601
  query_params: Optional[Dict[str, Union[str, int, float]]] = None,
591
602
  ):
@@ -595,13 +606,13 @@ class SearchCommands:
595
606
 
596
607
  ### Parameters
597
608
 
598
- **query**: This can be either an `AggregateRequest`, `Query` or string.
609
+ **query**: This can be either an `AggregateRequest` or `Query`.
599
610
  **limited**: If set to True, removes details of reader iterator.
600
611
  **query_params**: Define one or more value parameters.
601
612
  Each parameter has a name and a value.
602
613
 
603
614
  """
604
- st = time.time()
615
+ st = time.monotonic()
605
616
  cmd = [PROFILE_CMD, self.index_name, ""]
606
617
  if limited:
607
618
  cmd.append("LIMITED")
@@ -620,7 +631,7 @@ class SearchCommands:
620
631
  res = self.execute_command(*cmd)
621
632
 
622
633
  return self._parse_results(
623
- PROFILE_CMD, res, query=query, duration=(time.time() - st) * 1000.0
634
+ PROFILE_CMD, res, query=query, duration=(time.monotonic() - st) * 1000.0
624
635
  )
625
636
 
626
637
  def spellcheck(self, query, distance=None, include=None, exclude=None):
@@ -691,6 +702,10 @@ class SearchCommands:
691
702
  cmd = [DICT_DUMP_CMD, name]
692
703
  return self.execute_command(*cmd)
693
704
 
705
+ @deprecated_function(
706
+ version="8.0.0",
707
+ reason="deprecated since Redis 8.0, call config_set from core module instead",
708
+ )
694
709
  def config_set(self, option: str, value: str) -> bool:
695
710
  """Set runtime configuration option.
696
711
 
@@ -705,6 +720,10 @@ class SearchCommands:
705
720
  raw = self.execute_command(*cmd)
706
721
  return raw == "OK"
707
722
 
723
+ @deprecated_function(
724
+ version="8.0.0",
725
+ reason="deprecated since Redis 8.0, call config_get from core module instead",
726
+ )
708
727
  def config_get(self, option: str) -> str:
709
728
  """Get runtime configuration option value.
710
729
 
@@ -931,7 +950,7 @@ class AsyncSearchCommands(SearchCommands):
931
950
  For more information see `FT.SEARCH <https://redis.io/commands/ft.search>`_.
932
951
  """ # noqa
933
952
  args, query = self._mk_query_args(query, query_params=query_params)
934
- st = time.time()
953
+ st = time.monotonic()
935
954
 
936
955
  options = {}
937
956
  if get_protocol_version(self.client) not in ["3", 3]:
@@ -943,7 +962,7 @@ class AsyncSearchCommands(SearchCommands):
943
962
  return res
944
963
 
945
964
  return self._parse_results(
946
- SEARCH_CMD, res, query=query, duration=(time.time() - st) * 1000.0
965
+ SEARCH_CMD, res, query=query, duration=(time.monotonic() - st) * 1000.0
947
966
  )
948
967
 
949
968
  async def aggregate(
@@ -1006,6 +1025,10 @@ class AsyncSearchCommands(SearchCommands):
1006
1025
 
1007
1026
  return self._parse_results(SPELLCHECK_CMD, res)
1008
1027
 
1028
+ @deprecated_function(
1029
+ version="8.0.0",
1030
+ reason="deprecated since Redis 8.0, call config_set from core module instead",
1031
+ )
1009
1032
  async def config_set(self, option: str, value: str) -> bool:
1010
1033
  """Set runtime configuration option.
1011
1034
 
@@ -1020,6 +1043,10 @@ class AsyncSearchCommands(SearchCommands):
1020
1043
  raw = await self.execute_command(*cmd)
1021
1044
  return raw == "OK"
1022
1045
 
1046
+ @deprecated_function(
1047
+ version="8.0.0",
1048
+ reason="deprecated since Redis 8.0, call config_get from core module instead",
1049
+ )
1023
1050
  async def config_get(self, option: str) -> str:
1024
1051
  """Get runtime configuration option value.
1025
1052
 
@@ -0,0 +1,3 @@
1
+ # Value for the default dialect to be used as a part of
2
+ # Search or Aggregate query.
3
+ DEFAULT_DIALECT = 2
@@ -0,0 +1,14 @@
1
+ from typing import Any
2
+
3
+
4
+ class ProfileInformation:
5
+ """
6
+ Wrapper around FT.PROFILE response
7
+ """
8
+
9
+ def __init__(self, info: Any) -> None:
10
+ self._info: Any = info
11
+
12
+ @property
13
+ def info(self) -> Any:
14
+ return self._info
@@ -1,5 +1,7 @@
1
1
  from typing import List, Optional, Union
2
2
 
3
+ from redis.commands.search.dialect import DEFAULT_DIALECT
4
+
3
5
 
4
6
  class Query:
5
7
  """
@@ -40,7 +42,7 @@ class Query:
40
42
  self._highlight_fields: List = []
41
43
  self._language: Optional[str] = None
42
44
  self._expander: Optional[str] = None
43
- self._dialect: Optional[int] = None
45
+ self._dialect: int = DEFAULT_DIALECT
44
46
 
45
47
  def query_string(self) -> str:
46
48
  """Return the query string of this query only."""
@@ -177,6 +179,8 @@ class Query:
177
179
  Use a different scoring function to evaluate document relevance.
178
180
  Default is `TFIDF`.
179
181
 
182
+ Since Redis 8.0 default was changed to BM25STD.
183
+
180
184
  :param scorer: The scoring function to use
181
185
  (e.g. `TFIDF.DOCNORM` or `BM25`)
182
186
  """
@@ -84,7 +84,7 @@ class TimeSeries(TimeSeriesCommands):
84
84
  startup_nodes=self.client.nodes_manager.startup_nodes,
85
85
  result_callbacks=self.client.result_callbacks,
86
86
  cluster_response_callbacks=self.client.cluster_response_callbacks,
87
- cluster_error_retry_attempts=self.client.cluster_error_retry_attempts,
87
+ cluster_error_retry_attempts=self.client.retry.get_retries(),
88
88
  read_from_replicas=self.client.read_from_replicas,
89
89
  reinitialize_steps=self.client.reinitialize_steps,
90
90
  lock=self.client._lock,
@@ -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)