Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/redis/_parsers/helpers.py: 15%
375 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-23 06:16 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-23 06:16 +0000
1import datetime
3from redis.utils import str_if_bytes
6def timestamp_to_datetime(response):
7 "Converts a unix timestamp to a Python datetime object"
8 if not response:
9 return None
10 try:
11 response = int(response)
12 except ValueError:
13 return None
14 return datetime.datetime.fromtimestamp(response)
17def parse_debug_object(response):
18 "Parse the results of Redis's DEBUG OBJECT command into a Python dict"
19 # The 'type' of the object is the first item in the response, but isn't
20 # prefixed with a name
21 response = str_if_bytes(response)
22 response = "type:" + response
23 response = dict(kv.split(":") for kv in response.split())
25 # parse some expected int values from the string response
26 # note: this cmd isn't spec'd so these may not appear in all redis versions
27 int_fields = ("refcount", "serializedlength", "lru", "lru_seconds_idle")
28 for field in int_fields:
29 if field in response:
30 response[field] = int(response[field])
32 return response
35def parse_info(response):
36 """Parse the result of Redis's INFO command into a Python dict"""
37 info = {}
38 response = str_if_bytes(response)
40 def get_value(value):
41 if "," not in value or "=" not in value:
42 try:
43 if "." in value:
44 return float(value)
45 else:
46 return int(value)
47 except ValueError:
48 return value
49 else:
50 sub_dict = {}
51 for item in value.split(","):
52 k, v = item.rsplit("=", 1)
53 sub_dict[k] = get_value(v)
54 return sub_dict
56 for line in response.splitlines():
57 if line and not line.startswith("#"):
58 if line.find(":") != -1:
59 # Split, the info fields keys and values.
60 # Note that the value may contain ':'. but the 'host:'
61 # pseudo-command is the only case where the key contains ':'
62 key, value = line.split(":", 1)
63 if key == "cmdstat_host":
64 key, value = line.rsplit(":", 1)
66 if key == "module":
67 # Hardcode a list for key 'modules' since there could be
68 # multiple lines that started with 'module'
69 info.setdefault("modules", []).append(get_value(value))
70 else:
71 info[key] = get_value(value)
72 else:
73 # if the line isn't splittable, append it to the "__raw__" key
74 info.setdefault("__raw__", []).append(line)
76 return info
79def parse_memory_stats(response, **kwargs):
80 """Parse the results of MEMORY STATS"""
81 stats = pairs_to_dict(response, decode_keys=True, decode_string_values=True)
82 for key, value in stats.items():
83 if key.startswith("db."):
84 stats[key] = pairs_to_dict(
85 value, decode_keys=True, decode_string_values=True
86 )
87 return stats
90SENTINEL_STATE_TYPES = {
91 "can-failover-its-master": int,
92 "config-epoch": int,
93 "down-after-milliseconds": int,
94 "failover-timeout": int,
95 "info-refresh": int,
96 "last-hello-message": int,
97 "last-ok-ping-reply": int,
98 "last-ping-reply": int,
99 "last-ping-sent": int,
100 "master-link-down-time": int,
101 "master-port": int,
102 "num-other-sentinels": int,
103 "num-slaves": int,
104 "o-down-time": int,
105 "pending-commands": int,
106 "parallel-syncs": int,
107 "port": int,
108 "quorum": int,
109 "role-reported-time": int,
110 "s-down-time": int,
111 "slave-priority": int,
112 "slave-repl-offset": int,
113 "voted-leader-epoch": int,
114}
117def parse_sentinel_state(item):
118 result = pairs_to_dict_typed(item, SENTINEL_STATE_TYPES)
119 flags = set(result["flags"].split(","))
120 for name, flag in (
121 ("is_master", "master"),
122 ("is_slave", "slave"),
123 ("is_sdown", "s_down"),
124 ("is_odown", "o_down"),
125 ("is_sentinel", "sentinel"),
126 ("is_disconnected", "disconnected"),
127 ("is_master_down", "master_down"),
128 ):
129 result[name] = flag in flags
130 return result
133def parse_sentinel_master(response):
134 return parse_sentinel_state(map(str_if_bytes, response))
137def parse_sentinel_state_resp3(response):
138 result = {}
139 for key in response:
140 try:
141 value = SENTINEL_STATE_TYPES[key](str_if_bytes(response[key]))
142 result[str_if_bytes(key)] = value
143 except Exception:
144 result[str_if_bytes(key)] = response[str_if_bytes(key)]
145 flags = set(result["flags"].split(","))
146 result["flags"] = flags
147 return result
150def parse_sentinel_masters(response):
151 result = {}
152 for item in response:
153 state = parse_sentinel_state(map(str_if_bytes, item))
154 result[state["name"]] = state
155 return result
158def parse_sentinel_masters_resp3(response):
159 return [parse_sentinel_state(master) for master in response]
162def parse_sentinel_slaves_and_sentinels(response):
163 return [parse_sentinel_state(map(str_if_bytes, item)) for item in response]
166def parse_sentinel_slaves_and_sentinels_resp3(response):
167 return [parse_sentinel_state_resp3(item) for item in response]
170def parse_sentinel_get_master(response):
171 return response and (response[0], int(response[1])) or None
174def pairs_to_dict(response, decode_keys=False, decode_string_values=False):
175 """Create a dict given a list of key/value pairs"""
176 if response is None:
177 return {}
178 if decode_keys or decode_string_values:
179 # the iter form is faster, but I don't know how to make that work
180 # with a str_if_bytes() map
181 keys = response[::2]
182 if decode_keys:
183 keys = map(str_if_bytes, keys)
184 values = response[1::2]
185 if decode_string_values:
186 values = map(str_if_bytes, values)
187 return dict(zip(keys, values))
188 else:
189 it = iter(response)
190 return dict(zip(it, it))
193def pairs_to_dict_typed(response, type_info):
194 it = iter(response)
195 result = {}
196 for key, value in zip(it, it):
197 if key in type_info:
198 try:
199 value = type_info[key](value)
200 except Exception:
201 # if for some reason the value can't be coerced, just use
202 # the string value
203 pass
204 result[key] = value
205 return result
208def zset_score_pairs(response, **options):
209 """
210 If ``withscores`` is specified in the options, return the response as
211 a list of (value, score) pairs
212 """
213 if not response or not options.get("withscores"):
214 return response
215 score_cast_func = options.get("score_cast_func", float)
216 it = iter(response)
217 return list(zip(it, map(score_cast_func, it)))
220def sort_return_tuples(response, **options):
221 """
222 If ``groups`` is specified, return the response as a list of
223 n-element tuples with n being the value found in options['groups']
224 """
225 if not response or not options.get("groups"):
226 return response
227 n = options["groups"]
228 return list(zip(*[response[i::n] for i in range(n)]))
231def parse_stream_list(response):
232 if response is None:
233 return None
234 data = []
235 for r in response:
236 if r is not None:
237 data.append((r[0], pairs_to_dict(r[1])))
238 else:
239 data.append((None, None))
240 return data
243def pairs_to_dict_with_str_keys(response):
244 return pairs_to_dict(response, decode_keys=True)
247def parse_list_of_dicts(response):
248 return list(map(pairs_to_dict_with_str_keys, response))
251def parse_xclaim(response, **options):
252 if options.get("parse_justid", False):
253 return response
254 return parse_stream_list(response)
257def parse_xautoclaim(response, **options):
258 if options.get("parse_justid", False):
259 return response[1]
260 response[1] = parse_stream_list(response[1])
261 return response
264def parse_xinfo_stream(response, **options):
265 if isinstance(response, list):
266 data = pairs_to_dict(response, decode_keys=True)
267 else:
268 data = {str_if_bytes(k): v for k, v in response.items()}
269 if not options.get("full", False):
270 first = data.get("first-entry")
271 if first is not None:
272 data["first-entry"] = (first[0], pairs_to_dict(first[1]))
273 last = data["last-entry"]
274 if last is not None:
275 data["last-entry"] = (last[0], pairs_to_dict(last[1]))
276 else:
277 data["entries"] = {_id: pairs_to_dict(entry) for _id, entry in data["entries"]}
278 if isinstance(data["groups"][0], list):
279 data["groups"] = [
280 pairs_to_dict(group, decode_keys=True) for group in data["groups"]
281 ]
282 else:
283 data["groups"] = [
284 {str_if_bytes(k): v for k, v in group.items()}
285 for group in data["groups"]
286 ]
287 return data
290def parse_xread(response):
291 if response is None:
292 return []
293 return [[r[0], parse_stream_list(r[1])] for r in response]
296def parse_xread_resp3(response):
297 if response is None:
298 return {}
299 return {key: [parse_stream_list(value)] for key, value in response.items()}
302def parse_xpending(response, **options):
303 if options.get("parse_detail", False):
304 return parse_xpending_range(response)
305 consumers = [{"name": n, "pending": int(p)} for n, p in response[3] or []]
306 return {
307 "pending": response[0],
308 "min": response[1],
309 "max": response[2],
310 "consumers": consumers,
311 }
314def parse_xpending_range(response):
315 k = ("message_id", "consumer", "time_since_delivered", "times_delivered")
316 return [dict(zip(k, r)) for r in response]
319def float_or_none(response):
320 if response is None:
321 return None
322 return float(response)
325def bool_ok(response, **options):
326 return str_if_bytes(response) == "OK"
329def parse_zadd(response, **options):
330 if response is None:
331 return None
332 if options.get("as_score"):
333 return float(response)
334 return int(response)
337def parse_client_list(response, **options):
338 clients = []
339 for c in str_if_bytes(response).splitlines():
340 # Values might contain '='
341 clients.append(dict(pair.split("=", 1) for pair in c.split(" ")))
342 return clients
345def parse_config_get(response, **options):
346 response = [str_if_bytes(i) if i is not None else None for i in response]
347 return response and pairs_to_dict(response) or {}
350def parse_scan(response, **options):
351 cursor, r = response
352 return int(cursor), r
355def parse_hscan(response, **options):
356 cursor, r = response
357 return int(cursor), r and pairs_to_dict(r) or {}
360def parse_zscan(response, **options):
361 score_cast_func = options.get("score_cast_func", float)
362 cursor, r = response
363 it = iter(r)
364 return int(cursor), list(zip(it, map(score_cast_func, it)))
367def parse_zmscore(response, **options):
368 # zmscore: list of scores (double precision floating point number) or nil
369 return [float(score) if score is not None else None for score in response]
372def parse_slowlog_get(response, **options):
373 space = " " if options.get("decode_responses", False) else b" "
375 def parse_item(item):
376 result = {"id": item[0], "start_time": int(item[1]), "duration": int(item[2])}
377 # Redis Enterprise injects another entry at index [3], which has
378 # the complexity info (i.e. the value N in case the command has
379 # an O(N) complexity) instead of the command.
380 if isinstance(item[3], list):
381 result["command"] = space.join(item[3])
382 result["client_address"] = item[4]
383 result["client_name"] = item[5]
384 else:
385 result["complexity"] = item[3]
386 result["command"] = space.join(item[4])
387 result["client_address"] = item[5]
388 result["client_name"] = item[6]
389 return result
391 return [parse_item(item) for item in response]
394def parse_stralgo(response, **options):
395 """
396 Parse the response from `STRALGO` command.
397 Without modifiers the returned value is string.
398 When LEN is given the command returns the length of the result
399 (i.e integer).
400 When IDX is given the command returns a dictionary with the LCS
401 length and all the ranges in both the strings, start and end
402 offset for each string, where there are matches.
403 When WITHMATCHLEN is given, each array representing a match will
404 also have the length of the match at the beginning of the array.
405 """
406 if options.get("len", False):
407 return int(response)
408 if options.get("idx", False):
409 if options.get("withmatchlen", False):
410 matches = [
411 [(int(match[-1]))] + list(map(tuple, match[:-1]))
412 for match in response[1]
413 ]
414 else:
415 matches = [list(map(tuple, match)) for match in response[1]]
416 return {
417 str_if_bytes(response[0]): matches,
418 str_if_bytes(response[2]): int(response[3]),
419 }
420 return str_if_bytes(response)
423def parse_cluster_info(response, **options):
424 response = str_if_bytes(response)
425 return dict(line.split(":") for line in response.splitlines() if line)
428def _parse_node_line(line):
429 line_items = line.split(" ")
430 node_id, addr, flags, master_id, ping, pong, epoch, connected = line.split(" ")[:8]
431 addr = addr.split("@")[0]
432 node_dict = {
433 "node_id": node_id,
434 "flags": flags,
435 "master_id": master_id,
436 "last_ping_sent": ping,
437 "last_pong_rcvd": pong,
438 "epoch": epoch,
439 "slots": [],
440 "migrations": [],
441 "connected": True if connected == "connected" else False,
442 }
443 if len(line_items) >= 9:
444 slots, migrations = _parse_slots(line_items[8:])
445 node_dict["slots"], node_dict["migrations"] = slots, migrations
446 return addr, node_dict
449def _parse_slots(slot_ranges):
450 slots, migrations = [], []
451 for s_range in slot_ranges:
452 if "->-" in s_range:
453 slot_id, dst_node_id = s_range[1:-1].split("->-", 1)
454 migrations.append(
455 {"slot": slot_id, "node_id": dst_node_id, "state": "migrating"}
456 )
457 elif "-<-" in s_range:
458 slot_id, src_node_id = s_range[1:-1].split("-<-", 1)
459 migrations.append(
460 {"slot": slot_id, "node_id": src_node_id, "state": "importing"}
461 )
462 else:
463 s_range = [sl for sl in s_range.split("-")]
464 slots.append(s_range)
466 return slots, migrations
469def parse_cluster_nodes(response, **options):
470 """
471 @see: https://redis.io/commands/cluster-nodes # string / bytes
472 @see: https://redis.io/commands/cluster-replicas # list of string / bytes
473 """
474 if isinstance(response, (str, bytes)):
475 response = response.splitlines()
476 return dict(_parse_node_line(str_if_bytes(node)) for node in response)
479def parse_geosearch_generic(response, **options):
480 """
481 Parse the response of 'GEOSEARCH', GEORADIUS' and 'GEORADIUSBYMEMBER'
482 commands according to 'withdist', 'withhash' and 'withcoord' labels.
483 """
484 try:
485 if options["store"] or options["store_dist"]:
486 # `store` and `store_dist` cant be combined
487 # with other command arguments.
488 # relevant to 'GEORADIUS' and 'GEORADIUSBYMEMBER'
489 return response
490 except KeyError: # it means the command was sent via execute_command
491 return response
493 if type(response) != list:
494 response_list = [response]
495 else:
496 response_list = response
498 if not options["withdist"] and not options["withcoord"] and not options["withhash"]:
499 # just a bunch of places
500 return response_list
502 cast = {
503 "withdist": float,
504 "withcoord": lambda ll: (float(ll[0]), float(ll[1])),
505 "withhash": int,
506 }
508 # zip all output results with each casting function to get
509 # the properly native Python value.
510 f = [lambda x: x]
511 f += [cast[o] for o in ["withdist", "withhash", "withcoord"] if options[o]]
512 return [list(map(lambda fv: fv[0](fv[1]), zip(f, r))) for r in response_list]
515def parse_command(response, **options):
516 commands = {}
517 for command in response:
518 cmd_dict = {}
519 cmd_name = str_if_bytes(command[0])
520 cmd_dict["name"] = cmd_name
521 cmd_dict["arity"] = int(command[1])
522 cmd_dict["flags"] = [str_if_bytes(flag) for flag in command[2]]
523 cmd_dict["first_key_pos"] = command[3]
524 cmd_dict["last_key_pos"] = command[4]
525 cmd_dict["step_count"] = command[5]
526 if len(command) > 7:
527 cmd_dict["tips"] = command[7]
528 cmd_dict["key_specifications"] = command[8]
529 cmd_dict["subcommands"] = command[9]
530 commands[cmd_name] = cmd_dict
531 return commands
534def parse_command_resp3(response, **options):
535 commands = {}
536 for command in response:
537 cmd_dict = {}
538 cmd_name = str_if_bytes(command[0])
539 cmd_dict["name"] = cmd_name
540 cmd_dict["arity"] = command[1]
541 cmd_dict["flags"] = {str_if_bytes(flag) for flag in command[2]}
542 cmd_dict["first_key_pos"] = command[3]
543 cmd_dict["last_key_pos"] = command[4]
544 cmd_dict["step_count"] = command[5]
545 cmd_dict["acl_categories"] = command[6]
546 if len(command) > 7:
547 cmd_dict["tips"] = command[7]
548 cmd_dict["key_specifications"] = command[8]
549 cmd_dict["subcommands"] = command[9]
551 commands[cmd_name] = cmd_dict
552 return commands
555def parse_pubsub_numsub(response, **options):
556 return list(zip(response[0::2], response[1::2]))
559def parse_client_kill(response, **options):
560 if isinstance(response, int):
561 return response
562 return str_if_bytes(response) == "OK"
565def parse_acl_getuser(response, **options):
566 if response is None:
567 return None
568 if isinstance(response, list):
569 data = pairs_to_dict(response, decode_keys=True)
570 else:
571 data = {str_if_bytes(key): value for key, value in response.items()}
573 # convert everything but user-defined data in 'keys' to native strings
574 data["flags"] = list(map(str_if_bytes, data["flags"]))
575 data["passwords"] = list(map(str_if_bytes, data["passwords"]))
576 data["commands"] = str_if_bytes(data["commands"])
577 if isinstance(data["keys"], str) or isinstance(data["keys"], bytes):
578 data["keys"] = list(str_if_bytes(data["keys"]).split(" "))
579 if data["keys"] == [""]:
580 data["keys"] = []
581 if "channels" in data:
582 if isinstance(data["channels"], str) or isinstance(data["channels"], bytes):
583 data["channels"] = list(str_if_bytes(data["channels"]).split(" "))
584 if data["channels"] == [""]:
585 data["channels"] = []
586 if "selectors" in data:
587 if data["selectors"] != [] and isinstance(data["selectors"][0], list):
588 data["selectors"] = [
589 list(map(str_if_bytes, selector)) for selector in data["selectors"]
590 ]
591 elif data["selectors"] != []:
592 data["selectors"] = [
593 {str_if_bytes(k): str_if_bytes(v) for k, v in selector.items()}
594 for selector in data["selectors"]
595 ]
597 # split 'commands' into separate 'categories' and 'commands' lists
598 commands, categories = [], []
599 for command in data["commands"].split(" "):
600 categories.append(command) if "@" in command else commands.append(command)
602 data["commands"] = commands
603 data["categories"] = categories
604 data["enabled"] = "on" in data["flags"]
605 return data
608def parse_acl_log(response, **options):
609 if response is None:
610 return None
611 if isinstance(response, list):
612 data = []
613 for log in response:
614 log_data = pairs_to_dict(log, True, True)
615 client_info = log_data.get("client-info", "")
616 log_data["client-info"] = parse_client_info(client_info)
618 # float() is lossy comparing to the "double" in C
619 log_data["age-seconds"] = float(log_data["age-seconds"])
620 data.append(log_data)
621 else:
622 data = bool_ok(response)
623 return data
626def parse_client_info(value):
627 """
628 Parsing client-info in ACL Log in following format.
629 "key1=value1 key2=value2 key3=value3"
630 """
631 client_info = {}
632 for info in str_if_bytes(value).strip().split():
633 key, value = info.split("=")
634 client_info[key] = value
636 # Those fields are defined as int in networking.c
637 for int_key in {
638 "id",
639 "age",
640 "idle",
641 "db",
642 "sub",
643 "psub",
644 "multi",
645 "qbuf",
646 "qbuf-free",
647 "obl",
648 "argv-mem",
649 "oll",
650 "omem",
651 "tot-mem",
652 }:
653 client_info[int_key] = int(client_info[int_key])
654 return client_info
657def parse_set_result(response, **options):
658 """
659 Handle SET result since GET argument is available since Redis 6.2.
660 Parsing SET result into:
661 - BOOL
662 - String when GET argument is used
663 """
664 if options.get("get"):
665 # Redis will return a getCommand result.
666 # See `setGenericCommand` in t_string.c
667 return response
668 return response and str_if_bytes(response) == "OK"
671def string_keys_to_dict(key_string, callback):
672 return dict.fromkeys(key_string.split(), callback)
675_RedisCallbacks = {
676 **string_keys_to_dict(
677 "AUTH COPY EXPIRE EXPIREAT HEXISTS HMSET MOVE MSETNX PERSIST PSETEX "
678 "PEXPIRE PEXPIREAT RENAMENX SETEX SETNX SMOVE",
679 bool,
680 ),
681 **string_keys_to_dict("HINCRBYFLOAT INCRBYFLOAT", float),
682 **string_keys_to_dict(
683 "ASKING FLUSHALL FLUSHDB LSET LTRIM MSET PFMERGE READONLY READWRITE "
684 "RENAME SAVE SELECT SHUTDOWN SLAVEOF SWAPDB WATCH UNWATCH",
685 bool_ok,
686 ),
687 **string_keys_to_dict("XREAD XREADGROUP", parse_xread),
688 **string_keys_to_dict(
689 "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH",
690 parse_geosearch_generic,
691 ),
692 **string_keys_to_dict("XRANGE XREVRANGE", parse_stream_list),
693 "ACL GETUSER": parse_acl_getuser,
694 "ACL LOAD": bool_ok,
695 "ACL LOG": parse_acl_log,
696 "ACL SETUSER": bool_ok,
697 "ACL SAVE": bool_ok,
698 "CLIENT INFO": parse_client_info,
699 "CLIENT KILL": parse_client_kill,
700 "CLIENT LIST": parse_client_list,
701 "CLIENT PAUSE": bool_ok,
702 "CLIENT SETINFO": bool_ok,
703 "CLIENT SETNAME": bool_ok,
704 "CLIENT UNBLOCK": bool,
705 "CLUSTER ADDSLOTS": bool_ok,
706 "CLUSTER ADDSLOTSRANGE": bool_ok,
707 "CLUSTER DELSLOTS": bool_ok,
708 "CLUSTER DELSLOTSRANGE": bool_ok,
709 "CLUSTER FAILOVER": bool_ok,
710 "CLUSTER FORGET": bool_ok,
711 "CLUSTER INFO": parse_cluster_info,
712 "CLUSTER MEET": bool_ok,
713 "CLUSTER NODES": parse_cluster_nodes,
714 "CLUSTER REPLICAS": parse_cluster_nodes,
715 "CLUSTER REPLICATE": bool_ok,
716 "CLUSTER RESET": bool_ok,
717 "CLUSTER SAVECONFIG": bool_ok,
718 "CLUSTER SET-CONFIG-EPOCH": bool_ok,
719 "CLUSTER SETSLOT": bool_ok,
720 "CLUSTER SLAVES": parse_cluster_nodes,
721 "COMMAND": parse_command,
722 "CONFIG RESETSTAT": bool_ok,
723 "CONFIG SET": bool_ok,
724 "FUNCTION DELETE": bool_ok,
725 "FUNCTION FLUSH": bool_ok,
726 "FUNCTION RESTORE": bool_ok,
727 "GEODIST": float_or_none,
728 "HSCAN": parse_hscan,
729 "INFO": parse_info,
730 "LASTSAVE": timestamp_to_datetime,
731 "MEMORY PURGE": bool_ok,
732 "MODULE LOAD": bool,
733 "MODULE UNLOAD": bool,
734 "PING": lambda r: str_if_bytes(r) == "PONG",
735 "PUBSUB NUMSUB": parse_pubsub_numsub,
736 "PUBSUB SHARDNUMSUB": parse_pubsub_numsub,
737 "QUIT": bool_ok,
738 "SET": parse_set_result,
739 "SCAN": parse_scan,
740 "SCRIPT EXISTS": lambda r: list(map(bool, r)),
741 "SCRIPT FLUSH": bool_ok,
742 "SCRIPT KILL": bool_ok,
743 "SCRIPT LOAD": str_if_bytes,
744 "SENTINEL CKQUORUM": bool_ok,
745 "SENTINEL FAILOVER": bool_ok,
746 "SENTINEL FLUSHCONFIG": bool_ok,
747 "SENTINEL GET-MASTER-ADDR-BY-NAME": parse_sentinel_get_master,
748 "SENTINEL MONITOR": bool_ok,
749 "SENTINEL RESET": bool_ok,
750 "SENTINEL REMOVE": bool_ok,
751 "SENTINEL SET": bool_ok,
752 "SLOWLOG GET": parse_slowlog_get,
753 "SLOWLOG RESET": bool_ok,
754 "SORT": sort_return_tuples,
755 "SSCAN": parse_scan,
756 "TIME": lambda x: (int(x[0]), int(x[1])),
757 "XAUTOCLAIM": parse_xautoclaim,
758 "XCLAIM": parse_xclaim,
759 "XGROUP CREATE": bool_ok,
760 "XGROUP DESTROY": bool,
761 "XGROUP SETID": bool_ok,
762 "XINFO STREAM": parse_xinfo_stream,
763 "XPENDING": parse_xpending,
764 "ZSCAN": parse_zscan,
765}
768_RedisCallbacksRESP2 = {
769 **string_keys_to_dict(
770 "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
771 ),
772 **string_keys_to_dict(
773 "ZDIFF ZINTER ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYSCORE ZRANK ZREVRANGE "
774 "ZREVRANGEBYSCORE ZREVRANK ZUNION",
775 zset_score_pairs,
776 ),
777 **string_keys_to_dict("ZINCRBY ZSCORE", float_or_none),
778 **string_keys_to_dict("BGREWRITEAOF BGSAVE", lambda r: True),
779 **string_keys_to_dict("BLPOP BRPOP", lambda r: r and tuple(r) or None),
780 **string_keys_to_dict(
781 "BZPOPMAX BZPOPMIN", lambda r: r and (r[0], r[1], float(r[2])) or None
782 ),
783 "ACL CAT": lambda r: list(map(str_if_bytes, r)),
784 "ACL GENPASS": str_if_bytes,
785 "ACL HELP": lambda r: list(map(str_if_bytes, r)),
786 "ACL LIST": lambda r: list(map(str_if_bytes, r)),
787 "ACL USERS": lambda r: list(map(str_if_bytes, r)),
788 "ACL WHOAMI": str_if_bytes,
789 "CLIENT GETNAME": str_if_bytes,
790 "CLIENT TRACKINGINFO": lambda r: list(map(str_if_bytes, r)),
791 "CLUSTER GETKEYSINSLOT": lambda r: list(map(str_if_bytes, r)),
792 "COMMAND GETKEYS": lambda r: list(map(str_if_bytes, r)),
793 "CONFIG GET": parse_config_get,
794 "DEBUG OBJECT": parse_debug_object,
795 "GEOHASH": lambda r: list(map(str_if_bytes, r)),
796 "GEOPOS": lambda r: list(
797 map(lambda ll: (float(ll[0]), float(ll[1])) if ll is not None else None, r)
798 ),
799 "HGETALL": lambda r: r and pairs_to_dict(r) or {},
800 "MEMORY STATS": parse_memory_stats,
801 "MODULE LIST": lambda r: [pairs_to_dict(m) for m in r],
802 "RESET": str_if_bytes,
803 "SENTINEL MASTER": parse_sentinel_master,
804 "SENTINEL MASTERS": parse_sentinel_masters,
805 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels,
806 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels,
807 "STRALGO": parse_stralgo,
808 "XINFO CONSUMERS": parse_list_of_dicts,
809 "XINFO GROUPS": parse_list_of_dicts,
810 "ZADD": parse_zadd,
811 "ZMSCORE": parse_zmscore,
812}
815_RedisCallbacksRESP3 = {
816 **string_keys_to_dict(
817 "ZRANGE ZINTER ZPOPMAX ZPOPMIN ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE "
818 "ZUNION HGETALL XREADGROUP",
819 lambda r, **kwargs: r,
820 ),
821 **string_keys_to_dict("XREAD XREADGROUP", parse_xread_resp3),
822 "ACL LOG": lambda r: (
823 [
824 {str_if_bytes(key): str_if_bytes(value) for key, value in x.items()}
825 for x in r
826 ]
827 if isinstance(r, list)
828 else bool_ok(r)
829 ),
830 "COMMAND": parse_command_resp3,
831 "CONFIG GET": lambda r: {
832 str_if_bytes(key) if key is not None else None: (
833 str_if_bytes(value) if value is not None else None
834 )
835 for key, value in r.items()
836 },
837 "MEMORY STATS": lambda r: {str_if_bytes(key): value for key, value in r.items()},
838 "SENTINEL MASTER": parse_sentinel_state_resp3,
839 "SENTINEL MASTERS": parse_sentinel_masters_resp3,
840 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_resp3,
841 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_resp3,
842 "STRALGO": lambda r, **options: (
843 {str_if_bytes(key): str_if_bytes(value) for key, value in r.items()}
844 if isinstance(r, dict)
845 else str_if_bytes(r)
846 ),
847 "XINFO CONSUMERS": lambda r: [
848 {str_if_bytes(key): value for key, value in x.items()} for x in r
849 ],
850 "XINFO GROUPS": lambda r: [
851 {str_if_bytes(key): value for key, value in d.items()} for d in r
852 ],
853}