redis 5.3.0b5__py3-none-any.whl → 6.0.0b1__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 (41) hide show
  1. redis/__init__.py +2 -11
  2. redis/_parsers/base.py +14 -2
  3. redis/asyncio/client.py +20 -12
  4. redis/asyncio/cluster.py +79 -56
  5. redis/asyncio/connection.py +40 -11
  6. redis/asyncio/lock.py +26 -5
  7. redis/asyncio/sentinel.py +9 -1
  8. redis/asyncio/utils.py +1 -1
  9. redis/auth/token.py +6 -2
  10. redis/backoff.py +15 -0
  11. redis/client.py +21 -14
  12. redis/cluster.py +111 -49
  13. redis/commands/cluster.py +1 -11
  14. redis/commands/core.py +218 -206
  15. redis/commands/helpers.py +0 -70
  16. redis/commands/redismodules.py +0 -20
  17. redis/commands/search/aggregation.py +3 -1
  18. redis/commands/search/commands.py +41 -14
  19. redis/commands/search/dialect.py +3 -0
  20. redis/commands/search/profile_information.py +14 -0
  21. redis/commands/search/query.py +5 -1
  22. redis/connection.py +37 -19
  23. redis/exceptions.py +4 -1
  24. redis/lock.py +24 -4
  25. redis/ocsp.py +2 -1
  26. redis/sentinel.py +1 -1
  27. redis/utils.py +107 -1
  28. {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info}/METADATA +57 -23
  29. {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info}/RECORD +32 -39
  30. {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info}/WHEEL +1 -2
  31. redis/commands/graph/__init__.py +0 -263
  32. redis/commands/graph/commands.py +0 -313
  33. redis/commands/graph/edge.py +0 -91
  34. redis/commands/graph/exceptions.py +0 -3
  35. redis/commands/graph/execution_plan.py +0 -211
  36. redis/commands/graph/node.py +0 -88
  37. redis/commands/graph/path.py +0 -78
  38. redis/commands/graph/query_result.py +0 -588
  39. redis-5.3.0b5.dist-info/top_level.txt +0 -1
  40. /redis/commands/search/{indexDefinition.py → index_definition.py} +0 -0
  41. {redis-5.3.0b5.dist-info → redis-6.0.0b1.dist-info/licenses}/LICENSE +0 -0
@@ -1,313 +0,0 @@
1
- from redis import DataError
2
- from redis.exceptions import ResponseError
3
-
4
- from .exceptions import VersionMismatchException
5
- from .execution_plan import ExecutionPlan
6
- from .query_result import AsyncQueryResult, QueryResult
7
-
8
- PROFILE_CMD = "GRAPH.PROFILE"
9
- RO_QUERY_CMD = "GRAPH.RO_QUERY"
10
- QUERY_CMD = "GRAPH.QUERY"
11
- DELETE_CMD = "GRAPH.DELETE"
12
- SLOWLOG_CMD = "GRAPH.SLOWLOG"
13
- CONFIG_CMD = "GRAPH.CONFIG"
14
- LIST_CMD = "GRAPH.LIST"
15
- EXPLAIN_CMD = "GRAPH.EXPLAIN"
16
-
17
-
18
- class GraphCommands:
19
- """RedisGraph Commands"""
20
-
21
- def commit(self):
22
- """
23
- Create entire graph.
24
- """
25
- if len(self.nodes) == 0 and len(self.edges) == 0:
26
- return None
27
-
28
- query = "CREATE "
29
- for _, node in self.nodes.items():
30
- query += str(node) + ","
31
-
32
- query += ",".join([str(edge) for edge in self.edges])
33
-
34
- # Discard leading comma.
35
- if query[-1] == ",":
36
- query = query[:-1]
37
-
38
- return self.query(query)
39
-
40
- def query(self, q, params=None, timeout=None, read_only=False, profile=False):
41
- """
42
- Executes a query against the graph.
43
- For more information see `GRAPH.QUERY <https://redis.io/commands/graph.query>`_. # noqa
44
-
45
- Args:
46
-
47
- q : str
48
- The query.
49
- params : dict
50
- Query parameters.
51
- timeout : int
52
- Maximum runtime for read queries in milliseconds.
53
- read_only : bool
54
- Executes a readonly query if set to True.
55
- profile : bool
56
- Return details on results produced by and time
57
- spent in each operation.
58
- """
59
-
60
- # maintain original 'q'
61
- query = q
62
-
63
- # handle query parameters
64
- query = self._build_params_header(params) + query
65
-
66
- # construct query command
67
- # ask for compact result-set format
68
- # specify known graph version
69
- if profile:
70
- cmd = PROFILE_CMD
71
- else:
72
- cmd = RO_QUERY_CMD if read_only else QUERY_CMD
73
- command = [cmd, self.name, query, "--compact"]
74
-
75
- # include timeout is specified
76
- if isinstance(timeout, int):
77
- command.extend(["timeout", timeout])
78
- elif timeout is not None:
79
- raise Exception("Timeout argument must be a positive integer")
80
-
81
- # issue query
82
- try:
83
- response = self.execute_command(*command)
84
- return QueryResult(self, response, profile)
85
- except ResponseError as e:
86
- if "unknown command" in str(e) and read_only:
87
- # `GRAPH.RO_QUERY` is unavailable in older versions.
88
- return self.query(q, params, timeout, read_only=False)
89
- raise e
90
- except VersionMismatchException as e:
91
- # client view over the graph schema is out of sync
92
- # set client version and refresh local schema
93
- self.version = e.version
94
- self._refresh_schema()
95
- # re-issue query
96
- return self.query(q, params, timeout, read_only)
97
-
98
- def merge(self, pattern):
99
- """
100
- Merge pattern.
101
- """
102
- query = "MERGE "
103
- query += str(pattern)
104
-
105
- return self.query(query)
106
-
107
- def delete(self):
108
- """
109
- Deletes graph.
110
- For more information see `DELETE <https://redis.io/commands/graph.delete>`_. # noqa
111
- """
112
- self._clear_schema()
113
- return self.execute_command(DELETE_CMD, self.name)
114
-
115
- # declared here, to override the built in redis.db.flush()
116
- def flush(self):
117
- """
118
- Commit the graph and reset the edges and the nodes to zero length.
119
- """
120
- self.commit()
121
- self.nodes = {}
122
- self.edges = []
123
-
124
- def bulk(self, **kwargs):
125
- """Internal only. Not supported."""
126
- raise NotImplementedError(
127
- "GRAPH.BULK is internal only. "
128
- "Use https://github.com/redisgraph/redisgraph-bulk-loader."
129
- )
130
-
131
- def profile(self, query):
132
- """
133
- Execute a query and produce an execution plan augmented with metrics
134
- for each operation's execution. Return a string representation of a
135
- query execution plan, with details on results produced by and time
136
- spent in each operation.
137
- For more information see `GRAPH.PROFILE <https://redis.io/commands/graph.profile>`_. # noqa
138
- """
139
- return self.query(query, profile=True)
140
-
141
- def slowlog(self):
142
- """
143
- Get a list containing up to 10 of the slowest queries issued
144
- against the given graph ID.
145
- For more information see `GRAPH.SLOWLOG <https://redis.io/commands/graph.slowlog>`_. # noqa
146
-
147
- Each item in the list has the following structure:
148
- 1. A unix timestamp at which the log entry was processed.
149
- 2. The issued command.
150
- 3. The issued query.
151
- 4. The amount of time needed for its execution, in milliseconds.
152
- """
153
- return self.execute_command(SLOWLOG_CMD, self.name)
154
-
155
- def config(self, name, value=None, set=False):
156
- """
157
- Retrieve or update a RedisGraph configuration.
158
- For more information see `<https://redis.io/commands/graph.config-get/>`__.
159
-
160
- Args:
161
-
162
- name : str
163
- The name of the configuration
164
- value :
165
- The value we want to set (can be used only when `set` is on)
166
- set : bool
167
- Turn on to set a configuration. Default behavior is get.
168
- """
169
- params = ["SET" if set else "GET", name]
170
- if value is not None:
171
- if set:
172
- params.append(value)
173
- else:
174
- raise DataError(
175
- "``value`` can be provided only when ``set`` is True"
176
- ) # noqa
177
- return self.execute_command(CONFIG_CMD, *params)
178
-
179
- def list_keys(self):
180
- """
181
- Lists all graph keys in the keyspace.
182
- For more information see `GRAPH.LIST <https://redis.io/commands/graph.list>`_. # noqa
183
- """
184
- return self.execute_command(LIST_CMD)
185
-
186
- def execution_plan(self, query, params=None):
187
- """
188
- Get the execution plan for given query,
189
- GRAPH.EXPLAIN returns an array of operations.
190
-
191
- Args:
192
- query: the query that will be executed
193
- params: query parameters
194
- """
195
- query = self._build_params_header(params) + query
196
-
197
- plan = self.execute_command(EXPLAIN_CMD, self.name, query)
198
- if isinstance(plan[0], bytes):
199
- plan = [b.decode() for b in plan]
200
- return "\n".join(plan)
201
-
202
- def explain(self, query, params=None):
203
- """
204
- Get the execution plan for given query,
205
- GRAPH.EXPLAIN returns ExecutionPlan object.
206
- For more information see `GRAPH.EXPLAIN <https://redis.io/commands/graph.explain>`_. # noqa
207
-
208
- Args:
209
- query: the query that will be executed
210
- params: query parameters
211
- """
212
- query = self._build_params_header(params) + query
213
-
214
- plan = self.execute_command(EXPLAIN_CMD, self.name, query)
215
- return ExecutionPlan(plan)
216
-
217
-
218
- class AsyncGraphCommands(GraphCommands):
219
- async def query(self, q, params=None, timeout=None, read_only=False, profile=False):
220
- """
221
- Executes a query against the graph.
222
- For more information see `GRAPH.QUERY <https://oss.redis.com/redisgraph/master/commands/#graphquery>`_. # noqa
223
-
224
- Args:
225
-
226
- q : str
227
- The query.
228
- params : dict
229
- Query parameters.
230
- timeout : int
231
- Maximum runtime for read queries in milliseconds.
232
- read_only : bool
233
- Executes a readonly query if set to True.
234
- profile : bool
235
- Return details on results produced by and time
236
- spent in each operation.
237
- """
238
-
239
- # maintain original 'q'
240
- query = q
241
-
242
- # handle query parameters
243
- query = self._build_params_header(params) + query
244
-
245
- # construct query command
246
- # ask for compact result-set format
247
- # specify known graph version
248
- if profile:
249
- cmd = PROFILE_CMD
250
- else:
251
- cmd = RO_QUERY_CMD if read_only else QUERY_CMD
252
- command = [cmd, self.name, query, "--compact"]
253
-
254
- # include timeout is specified
255
- if isinstance(timeout, int):
256
- command.extend(["timeout", timeout])
257
- elif timeout is not None:
258
- raise Exception("Timeout argument must be a positive integer")
259
-
260
- # issue query
261
- try:
262
- response = await self.execute_command(*command)
263
- return await AsyncQueryResult().initialize(self, response, profile)
264
- except ResponseError as e:
265
- if "unknown command" in str(e) and read_only:
266
- # `GRAPH.RO_QUERY` is unavailable in older versions.
267
- return await self.query(q, params, timeout, read_only=False)
268
- raise e
269
- except VersionMismatchException as e:
270
- # client view over the graph schema is out of sync
271
- # set client version and refresh local schema
272
- self.version = e.version
273
- self._refresh_schema()
274
- # re-issue query
275
- return await self.query(q, params, timeout, read_only)
276
-
277
- async def execution_plan(self, query, params=None):
278
- """
279
- Get the execution plan for given query,
280
- GRAPH.EXPLAIN returns an array of operations.
281
-
282
- Args:
283
- query: the query that will be executed
284
- params: query parameters
285
- """
286
- query = self._build_params_header(params) + query
287
-
288
- plan = await self.execute_command(EXPLAIN_CMD, self.name, query)
289
- if isinstance(plan[0], bytes):
290
- plan = [b.decode() for b in plan]
291
- return "\n".join(plan)
292
-
293
- async def explain(self, query, params=None):
294
- """
295
- Get the execution plan for given query,
296
- GRAPH.EXPLAIN returns ExecutionPlan object.
297
-
298
- Args:
299
- query: the query that will be executed
300
- params: query parameters
301
- """
302
- query = self._build_params_header(params) + query
303
-
304
- plan = await self.execute_command(EXPLAIN_CMD, self.name, query)
305
- return ExecutionPlan(plan)
306
-
307
- async def flush(self):
308
- """
309
- Commit the graph and reset the edges and the nodes to zero length.
310
- """
311
- await self.commit()
312
- self.nodes = {}
313
- self.edges = []
@@ -1,91 +0,0 @@
1
- from ..helpers import quote_string
2
- from .node import Node
3
-
4
-
5
- class Edge:
6
- """
7
- An edge connecting two nodes.
8
- """
9
-
10
- def __init__(self, src_node, relation, dest_node, edge_id=None, properties=None):
11
- """
12
- Create a new edge.
13
- """
14
- if src_node is None or dest_node is None:
15
- # NOTE(bors-42): It makes sense to change AssertionError to
16
- # ValueError here
17
- raise AssertionError("Both src_node & dest_node must be provided")
18
-
19
- self.id = edge_id
20
- self.relation = relation or ""
21
- self.properties = properties or {}
22
- self.src_node = src_node
23
- self.dest_node = dest_node
24
-
25
- def to_string(self):
26
- res = ""
27
- if self.properties:
28
- props = ",".join(
29
- key + ":" + str(quote_string(val))
30
- for key, val in sorted(self.properties.items())
31
- )
32
- res += "{" + props + "}"
33
-
34
- return res
35
-
36
- def __str__(self):
37
- # Source node.
38
- if isinstance(self.src_node, Node):
39
- res = str(self.src_node)
40
- else:
41
- res = "()"
42
-
43
- # Edge
44
- res += "-["
45
- if self.relation:
46
- res += ":" + self.relation
47
- if self.properties:
48
- props = ",".join(
49
- key + ":" + str(quote_string(val))
50
- for key, val in sorted(self.properties.items())
51
- )
52
- res += "{" + props + "}"
53
- res += "]->"
54
-
55
- # Dest node.
56
- if isinstance(self.dest_node, Node):
57
- res += str(self.dest_node)
58
- else:
59
- res += "()"
60
-
61
- return res
62
-
63
- def __eq__(self, rhs):
64
- # Type checking
65
- if not isinstance(rhs, Edge):
66
- return False
67
-
68
- # Quick positive check, if both IDs are set.
69
- if self.id is not None and rhs.id is not None and self.id == rhs.id:
70
- return True
71
-
72
- # Source and destination nodes should match.
73
- if self.src_node != rhs.src_node:
74
- return False
75
-
76
- if self.dest_node != rhs.dest_node:
77
- return False
78
-
79
- # Relation should match.
80
- if self.relation != rhs.relation:
81
- return False
82
-
83
- # Quick check for number of properties.
84
- if len(self.properties) != len(rhs.properties):
85
- return False
86
-
87
- # Compare properties.
88
- if self.properties != rhs.properties:
89
- return False
90
-
91
- return True
@@ -1,3 +0,0 @@
1
- class VersionMismatchException(Exception):
2
- def __init__(self, version):
3
- self.version = version
@@ -1,211 +0,0 @@
1
- import re
2
-
3
-
4
- class ProfileStats:
5
- """
6
- ProfileStats, runtime execution statistics of operation.
7
- """
8
-
9
- def __init__(self, records_produced, execution_time):
10
- self.records_produced = records_produced
11
- self.execution_time = execution_time
12
-
13
-
14
- class Operation:
15
- """
16
- Operation, single operation within execution plan.
17
- """
18
-
19
- def __init__(self, name, args=None, profile_stats=None):
20
- """
21
- Create a new operation.
22
-
23
- Args:
24
- name: string that represents the name of the operation
25
- args: operation arguments
26
- profile_stats: profile statistics
27
- """
28
- self.name = name
29
- self.args = args
30
- self.profile_stats = profile_stats
31
- self.children = []
32
-
33
- def append_child(self, child):
34
- if not isinstance(child, Operation) or self is child:
35
- raise Exception("child must be Operation")
36
-
37
- self.children.append(child)
38
- return self
39
-
40
- def child_count(self):
41
- return len(self.children)
42
-
43
- def __eq__(self, o: object) -> bool:
44
- if not isinstance(o, Operation):
45
- return False
46
-
47
- return self.name == o.name and self.args == o.args
48
-
49
- def __str__(self) -> str:
50
- args_str = "" if self.args is None else " | " + self.args
51
- return f"{self.name}{args_str}"
52
-
53
-
54
- class ExecutionPlan:
55
- """
56
- ExecutionPlan, collection of operations.
57
- """
58
-
59
- def __init__(self, plan):
60
- """
61
- Create a new execution plan.
62
-
63
- Args:
64
- plan: array of strings that represents the collection operations
65
- the output from GRAPH.EXPLAIN
66
- """
67
- if not isinstance(plan, list):
68
- raise Exception("plan must be an array")
69
-
70
- if isinstance(plan[0], bytes):
71
- plan = [b.decode() for b in plan]
72
-
73
- self.plan = plan
74
- self.structured_plan = self._operation_tree()
75
-
76
- def _compare_operations(self, root_a, root_b):
77
- """
78
- Compare execution plan operation tree
79
-
80
- Return: True if operation trees are equal, False otherwise
81
- """
82
-
83
- # compare current root
84
- if root_a != root_b:
85
- return False
86
-
87
- # make sure root have the same number of children
88
- if root_a.child_count() != root_b.child_count():
89
- return False
90
-
91
- # recursively compare children
92
- for i in range(root_a.child_count()):
93
- if not self._compare_operations(root_a.children[i], root_b.children[i]):
94
- return False
95
-
96
- return True
97
-
98
- def __str__(self) -> str:
99
- def aggraget_str(str_children):
100
- return "\n".join(
101
- [
102
- " " + line
103
- for str_child in str_children
104
- for line in str_child.splitlines()
105
- ]
106
- )
107
-
108
- def combine_str(x, y):
109
- return f"{x}\n{y}"
110
-
111
- return self._operation_traverse(
112
- self.structured_plan, str, aggraget_str, combine_str
113
- )
114
-
115
- def __eq__(self, o: object) -> bool:
116
- """Compares two execution plans
117
-
118
- Return: True if the two plans are equal False otherwise
119
- """
120
- # make sure 'o' is an execution-plan
121
- if not isinstance(o, ExecutionPlan):
122
- return False
123
-
124
- # get root for both plans
125
- root_a = self.structured_plan
126
- root_b = o.structured_plan
127
-
128
- # compare execution trees
129
- return self._compare_operations(root_a, root_b)
130
-
131
- def _operation_traverse(self, op, op_f, aggregate_f, combine_f):
132
- """
133
- Traverse operation tree recursively applying functions
134
-
135
- Args:
136
- op: operation to traverse
137
- op_f: function applied for each operation
138
- aggregate_f: aggregation function applied for all children of a single operation
139
- combine_f: combine function applied for the operation result and the children result
140
- """ # noqa
141
- # apply op_f for each operation
142
- op_res = op_f(op)
143
- if len(op.children) == 0:
144
- return op_res # no children return
145
- else:
146
- # apply _operation_traverse recursively
147
- children = [
148
- self._operation_traverse(child, op_f, aggregate_f, combine_f)
149
- for child in op.children
150
- ]
151
- # combine the operation result with the children aggregated result
152
- return combine_f(op_res, aggregate_f(children))
153
-
154
- def _operation_tree(self):
155
- """Build the operation tree from the string representation"""
156
-
157
- # initial state
158
- i = 0
159
- level = 0
160
- stack = []
161
- current = None
162
-
163
- def _create_operation(args):
164
- profile_stats = None
165
- name = args[0].strip()
166
- args.pop(0)
167
- if len(args) > 0 and "Records produced" in args[-1]:
168
- records_produced = int(
169
- re.search("Records produced: (\\d+)", args[-1]).group(1)
170
- )
171
- execution_time = float(
172
- re.search("Execution time: (\\d+.\\d+) ms", args[-1]).group(1)
173
- )
174
- profile_stats = ProfileStats(records_produced, execution_time)
175
- args.pop(-1)
176
- return Operation(
177
- name, None if len(args) == 0 else args[0].strip(), profile_stats
178
- )
179
-
180
- # iterate plan operations
181
- while i < len(self.plan):
182
- current_op = self.plan[i]
183
- op_level = current_op.count(" ")
184
- if op_level == level:
185
- # if the operation level equal to the current level
186
- # set the current operation and move next
187
- child = _create_operation(current_op.split("|"))
188
- if current:
189
- current = stack.pop()
190
- current.append_child(child)
191
- current = child
192
- i += 1
193
- elif op_level == level + 1:
194
- # if the operation is child of the current operation
195
- # add it as child and set as current operation
196
- child = _create_operation(current_op.split("|"))
197
- current.append_child(child)
198
- stack.append(current)
199
- current = child
200
- level += 1
201
- i += 1
202
- elif op_level < level:
203
- # if the operation is not child of current operation
204
- # go back to it's parent operation
205
- levels_back = level - op_level + 1
206
- for _ in range(levels_back):
207
- current = stack.pop()
208
- level -= levels_back
209
- else:
210
- raise Exception("corrupted plan")
211
- return stack[0]
@@ -1,88 +0,0 @@
1
- from ..helpers import quote_string
2
-
3
-
4
- class Node:
5
- """
6
- A node within the graph.
7
- """
8
-
9
- def __init__(self, node_id=None, alias=None, label=None, properties=None):
10
- """
11
- Create a new node.
12
- """
13
- self.id = node_id
14
- self.alias = alias
15
- if isinstance(label, list):
16
- label = [inner_label for inner_label in label if inner_label != ""]
17
-
18
- if (
19
- label is None
20
- or label == ""
21
- or (isinstance(label, list) and len(label) == 0)
22
- ):
23
- self.label = None
24
- self.labels = None
25
- elif isinstance(label, str):
26
- self.label = label
27
- self.labels = [label]
28
- elif isinstance(label, list) and all(
29
- [isinstance(inner_label, str) for inner_label in label]
30
- ):
31
- self.label = label[0]
32
- self.labels = label
33
- else:
34
- raise AssertionError(
35
- "label should be either None, string or a list of strings"
36
- )
37
-
38
- self.properties = properties or {}
39
-
40
- def to_string(self):
41
- res = ""
42
- if self.properties:
43
- props = ",".join(
44
- key + ":" + str(quote_string(val))
45
- for key, val in sorted(self.properties.items())
46
- )
47
- res += "{" + props + "}"
48
-
49
- return res
50
-
51
- def __str__(self):
52
- res = "("
53
- if self.alias:
54
- res += self.alias
55
- if self.labels:
56
- res += ":" + ":".join(self.labels)
57
- if self.properties:
58
- props = ",".join(
59
- key + ":" + str(quote_string(val))
60
- for key, val in sorted(self.properties.items())
61
- )
62
- res += "{" + props + "}"
63
- res += ")"
64
-
65
- return res
66
-
67
- def __eq__(self, rhs):
68
- # Type checking
69
- if not isinstance(rhs, Node):
70
- return False
71
-
72
- # Quick positive check, if both IDs are set.
73
- if self.id is not None and rhs.id is not None and self.id != rhs.id:
74
- return False
75
-
76
- # Label should match.
77
- if self.label != rhs.label:
78
- return False
79
-
80
- # Quick check for number of properties.
81
- if len(self.properties) != len(rhs.properties):
82
- return False
83
-
84
- # Compare properties.
85
- if self.properties != rhs.properties:
86
- return False
87
-
88
- return True