Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/redis/_parsers/helpers.py: 15%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

391 statements  

1import datetime 

2 

3from redis.utils import str_if_bytes 

4 

5 

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) 

15 

16 

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()) 

24 

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]) 

31 

32 return response 

33 

34 

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) 

39 

40 def get_value(value): 

41 if "," not in value and "=" 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 elif "=" not in value: 

50 return [get_value(v) for v in value.split(",") if v] 

51 else: 

52 sub_dict = {} 

53 for item in value.split(","): 

54 if not item: 

55 continue 

56 if "=" in item: 

57 k, v = item.rsplit("=", 1) 

58 sub_dict[k] = get_value(v) 

59 else: 

60 sub_dict[item] = True 

61 return sub_dict 

62 

63 for line in response.splitlines(): 

64 if line and not line.startswith("#"): 

65 if line.find(":") != -1: 

66 # Split, the info fields keys and values. 

67 # Note that the value may contain ':'. but the 'host:' 

68 # pseudo-command is the only case where the key contains ':' 

69 key, value = line.split(":", 1) 

70 if key == "cmdstat_host": 

71 key, value = line.rsplit(":", 1) 

72 

73 if key == "module": 

74 # Hardcode a list for key 'modules' since there could be 

75 # multiple lines that started with 'module' 

76 info.setdefault("modules", []).append(get_value(value)) 

77 else: 

78 info[key] = get_value(value) 

79 else: 

80 # if the line isn't splittable, append it to the "__raw__" key 

81 info.setdefault("__raw__", []).append(line) 

82 

83 return info 

84 

85 

86def parse_memory_stats(response, **kwargs): 

87 """Parse the results of MEMORY STATS""" 

88 stats = pairs_to_dict(response, decode_keys=True, decode_string_values=True) 

89 for key, value in stats.items(): 

90 if key.startswith("db.") and isinstance(value, list): 

91 stats[key] = pairs_to_dict( 

92 value, decode_keys=True, decode_string_values=True 

93 ) 

94 return stats 

95 

96 

97SENTINEL_STATE_TYPES = { 

98 "can-failover-its-master": int, 

99 "config-epoch": int, 

100 "down-after-milliseconds": int, 

101 "failover-timeout": int, 

102 "info-refresh": int, 

103 "last-hello-message": int, 

104 "last-ok-ping-reply": int, 

105 "last-ping-reply": int, 

106 "last-ping-sent": int, 

107 "master-link-down-time": int, 

108 "master-port": int, 

109 "num-other-sentinels": int, 

110 "num-slaves": int, 

111 "o-down-time": int, 

112 "pending-commands": int, 

113 "parallel-syncs": int, 

114 "port": int, 

115 "quorum": int, 

116 "role-reported-time": int, 

117 "s-down-time": int, 

118 "slave-priority": int, 

119 "slave-repl-offset": int, 

120 "voted-leader-epoch": int, 

121} 

122 

123 

124def parse_sentinel_state(item): 

125 result = pairs_to_dict_typed(item, SENTINEL_STATE_TYPES) 

126 flags = set(result["flags"].split(",")) 

127 for name, flag in ( 

128 ("is_master", "master"), 

129 ("is_slave", "slave"), 

130 ("is_sdown", "s_down"), 

131 ("is_odown", "o_down"), 

132 ("is_sentinel", "sentinel"), 

133 ("is_disconnected", "disconnected"), 

134 ("is_master_down", "master_down"), 

135 ): 

136 result[name] = flag in flags 

137 return result 

138 

139 

140def parse_sentinel_master(response): 

141 return parse_sentinel_state(map(str_if_bytes, response)) 

142 

143 

144def parse_sentinel_state_resp3(response): 

145 result = {} 

146 for key in response: 

147 try: 

148 value = SENTINEL_STATE_TYPES[key](str_if_bytes(response[key])) 

149 result[str_if_bytes(key)] = value 

150 except Exception: 

151 result[str_if_bytes(key)] = response[str_if_bytes(key)] 

152 flags = set(result["flags"].split(",")) 

153 result["flags"] = flags 

154 return result 

155 

156 

157def parse_sentinel_masters(response): 

158 result = {} 

159 for item in response: 

160 state = parse_sentinel_state(map(str_if_bytes, item)) 

161 result[state["name"]] = state 

162 return result 

163 

164 

165def parse_sentinel_masters_resp3(response): 

166 return [parse_sentinel_state(master) for master in response] 

167 

168 

169def parse_sentinel_slaves_and_sentinels(response): 

170 return [parse_sentinel_state(map(str_if_bytes, item)) for item in response] 

171 

172 

173def parse_sentinel_slaves_and_sentinels_resp3(response): 

174 return [parse_sentinel_state_resp3(item) for item in response] 

175 

176 

177def parse_sentinel_get_master(response): 

178 return response and (response[0], int(response[1])) or None 

179 

180 

181def pairs_to_dict(response, decode_keys=False, decode_string_values=False): 

182 """Create a dict given a list of key/value pairs""" 

183 if response is None: 

184 return {} 

185 if decode_keys or decode_string_values: 

186 # the iter form is faster, but I don't know how to make that work 

187 # with a str_if_bytes() map 

188 keys = response[::2] 

189 if decode_keys: 

190 keys = map(str_if_bytes, keys) 

191 values = response[1::2] 

192 if decode_string_values: 

193 values = map(str_if_bytes, values) 

194 return dict(zip(keys, values)) 

195 else: 

196 it = iter(response) 

197 return dict(zip(it, it)) 

198 

199 

200def pairs_to_dict_typed(response, type_info): 

201 it = iter(response) 

202 result = {} 

203 for key, value in zip(it, it): 

204 if key in type_info: 

205 try: 

206 value = type_info[key](value) 

207 except Exception: 

208 # if for some reason the value can't be coerced, just use 

209 # the string value 

210 pass 

211 result[key] = value 

212 return result 

213 

214 

215def zset_score_pairs(response, **options): 

216 """ 

217 If ``withscores`` is specified in the options, return the response as 

218 a list of (value, score) pairs 

219 """ 

220 if not response or not options.get("withscores"): 

221 return response 

222 score_cast_func = options.get("score_cast_func", float) 

223 it = iter(response) 

224 return list(zip(it, map(score_cast_func, it))) 

225 

226 

227def sort_return_tuples(response, **options): 

228 """ 

229 If ``groups`` is specified, return the response as a list of 

230 n-element tuples with n being the value found in options['groups'] 

231 """ 

232 if not response or not options.get("groups"): 

233 return response 

234 n = options["groups"] 

235 return list(zip(*[response[i::n] for i in range(n)])) 

236 

237 

238def parse_stream_list(response): 

239 if response is None: 

240 return None 

241 data = [] 

242 for r in response: 

243 if r is not None: 

244 data.append((r[0], pairs_to_dict(r[1]))) 

245 else: 

246 data.append((None, None)) 

247 return data 

248 

249 

250def pairs_to_dict_with_str_keys(response): 

251 return pairs_to_dict(response, decode_keys=True) 

252 

253 

254def parse_list_of_dicts(response): 

255 return list(map(pairs_to_dict_with_str_keys, response)) 

256 

257 

258def parse_xclaim(response, **options): 

259 if options.get("parse_justid", False): 

260 return response 

261 return parse_stream_list(response) 

262 

263 

264def parse_xautoclaim(response, **options): 

265 if options.get("parse_justid", False): 

266 return response[1] 

267 response[1] = parse_stream_list(response[1]) 

268 return response 

269 

270 

271def parse_xinfo_stream(response, **options): 

272 if isinstance(response, list): 

273 data = pairs_to_dict(response, decode_keys=True) 

274 else: 

275 data = {str_if_bytes(k): v for k, v in response.items()} 

276 if not options.get("full", False): 

277 first = data.get("first-entry") 

278 if first is not None and first[0] is not None: 

279 data["first-entry"] = (first[0], pairs_to_dict(first[1])) 

280 last = data["last-entry"] 

281 if last is not None and last[0] is not None: 

282 data["last-entry"] = (last[0], pairs_to_dict(last[1])) 

283 else: 

284 data["entries"] = {_id: pairs_to_dict(entry) for _id, entry in data["entries"]} 

285 if len(data["groups"]) > 0 and isinstance(data["groups"][0], list): 

286 data["groups"] = [ 

287 pairs_to_dict(group, decode_keys=True) for group in data["groups"] 

288 ] 

289 for g in data["groups"]: 

290 if g["consumers"] and g["consumers"][0] is not None: 

291 g["consumers"] = [ 

292 pairs_to_dict(c, decode_keys=True) for c in g["consumers"] 

293 ] 

294 else: 

295 data["groups"] = [ 

296 {str_if_bytes(k): v for k, v in group.items()} 

297 for group in data["groups"] 

298 ] 

299 return data 

300 

301 

302def parse_xread(response): 

303 if response is None: 

304 return [] 

305 return [[r[0], parse_stream_list(r[1])] for r in response] 

306 

307 

308def parse_xread_resp3(response): 

309 if response is None: 

310 return {} 

311 return {key: [parse_stream_list(value)] for key, value in response.items()} 

312 

313 

314def parse_xpending(response, **options): 

315 if options.get("parse_detail", False): 

316 return parse_xpending_range(response) 

317 consumers = [{"name": n, "pending": int(p)} for n, p in response[3] or []] 

318 return { 

319 "pending": response[0], 

320 "min": response[1], 

321 "max": response[2], 

322 "consumers": consumers, 

323 } 

324 

325 

326def parse_xpending_range(response): 

327 k = ("message_id", "consumer", "time_since_delivered", "times_delivered") 

328 return [dict(zip(k, r)) for r in response] 

329 

330 

331def float_or_none(response): 

332 if response is None: 

333 return None 

334 return float(response) 

335 

336 

337def bool_ok(response, **options): 

338 return str_if_bytes(response) == "OK" 

339 

340 

341def parse_zadd(response, **options): 

342 if response is None: 

343 return None 

344 if options.get("as_score"): 

345 return float(response) 

346 return int(response) 

347 

348 

349def parse_client_list(response, **options): 

350 clients = [] 

351 for c in str_if_bytes(response).splitlines(): 

352 # Values might contain '=' 

353 clients.append(dict(pair.split("=", 1) for pair in c.split(" "))) 

354 return clients 

355 

356 

357def parse_config_get(response, **options): 

358 response = [str_if_bytes(i) if i is not None else None for i in response] 

359 return response and pairs_to_dict(response) or {} 

360 

361 

362def parse_scan(response, **options): 

363 cursor, r = response 

364 return int(cursor), r 

365 

366 

367def parse_hscan(response, **options): 

368 cursor, r = response 

369 no_values = options.get("no_values", False) 

370 if no_values: 

371 payload = r or [] 

372 else: 

373 payload = r and pairs_to_dict(r) or {} 

374 return int(cursor), payload 

375 

376 

377def parse_zscan(response, **options): 

378 score_cast_func = options.get("score_cast_func", float) 

379 cursor, r = response 

380 it = iter(r) 

381 return int(cursor), list(zip(it, map(score_cast_func, it))) 

382 

383 

384def parse_zmscore(response, **options): 

385 # zmscore: list of scores (double precision floating point number) or nil 

386 return [float(score) if score is not None else None for score in response] 

387 

388 

389def parse_slowlog_get(response, **options): 

390 space = " " if options.get("decode_responses", False) else b" " 

391 

392 def parse_item(item): 

393 result = {"id": item[0], "start_time": int(item[1]), "duration": int(item[2])} 

394 # Redis Enterprise injects another entry at index [3], which has 

395 # the complexity info (i.e. the value N in case the command has 

396 # an O(N) complexity) instead of the command. 

397 if isinstance(item[3], list): 

398 result["command"] = space.join(item[3]) 

399 

400 # These fields are optional, depends on environment. 

401 if len(item) >= 6: 

402 result["client_address"] = item[4] 

403 result["client_name"] = item[5] 

404 else: 

405 result["complexity"] = item[3] 

406 result["command"] = space.join(item[4]) 

407 

408 # These fields are optional, depends on environment. 

409 if len(item) >= 7: 

410 result["client_address"] = item[5] 

411 result["client_name"] = item[6] 

412 

413 return result 

414 

415 return [parse_item(item) for item in response] 

416 

417 

418def parse_stralgo(response, **options): 

419 """ 

420 Parse the response from `STRALGO` command. 

421 Without modifiers the returned value is string. 

422 When LEN is given the command returns the length of the result 

423 (i.e integer). 

424 When IDX is given the command returns a dictionary with the LCS 

425 length and all the ranges in both the strings, start and end 

426 offset for each string, where there are matches. 

427 When WITHMATCHLEN is given, each array representing a match will 

428 also have the length of the match at the beginning of the array. 

429 """ 

430 if options.get("len", False): 

431 return int(response) 

432 if options.get("idx", False): 

433 if options.get("withmatchlen", False): 

434 matches = [ 

435 [(int(match[-1]))] + list(map(tuple, match[:-1])) 

436 for match in response[1] 

437 ] 

438 else: 

439 matches = [list(map(tuple, match)) for match in response[1]] 

440 return { 

441 str_if_bytes(response[0]): matches, 

442 str_if_bytes(response[2]): int(response[3]), 

443 } 

444 return str_if_bytes(response) 

445 

446 

447def parse_cluster_info(response, **options): 

448 response = str_if_bytes(response) 

449 return dict(line.split(":") for line in response.splitlines() if line) 

450 

451 

452def _parse_node_line(line): 

453 line_items = line.split(" ") 

454 node_id, addr, flags, master_id, ping, pong, epoch, connected = line.split(" ")[:8] 

455 ip = addr.split("@")[0] 

456 hostname = addr.split("@")[1].split(",")[1] if "@" in addr and "," in addr else "" 

457 node_dict = { 

458 "node_id": node_id, 

459 "hostname": hostname, 

460 "flags": flags, 

461 "master_id": master_id, 

462 "last_ping_sent": ping, 

463 "last_pong_rcvd": pong, 

464 "epoch": epoch, 

465 "slots": [], 

466 "migrations": [], 

467 "connected": True if connected == "connected" else False, 

468 } 

469 if len(line_items) >= 9: 

470 slots, migrations = _parse_slots(line_items[8:]) 

471 node_dict["slots"], node_dict["migrations"] = slots, migrations 

472 return ip, node_dict 

473 

474 

475def _parse_slots(slot_ranges): 

476 slots, migrations = [], [] 

477 for s_range in slot_ranges: 

478 if "->-" in s_range: 

479 slot_id, dst_node_id = s_range[1:-1].split("->-", 1) 

480 migrations.append( 

481 {"slot": slot_id, "node_id": dst_node_id, "state": "migrating"} 

482 ) 

483 elif "-<-" in s_range: 

484 slot_id, src_node_id = s_range[1:-1].split("-<-", 1) 

485 migrations.append( 

486 {"slot": slot_id, "node_id": src_node_id, "state": "importing"} 

487 ) 

488 else: 

489 s_range = [sl for sl in s_range.split("-")] 

490 slots.append(s_range) 

491 

492 return slots, migrations 

493 

494 

495def parse_cluster_nodes(response, **options): 

496 """ 

497 @see: https://redis.io/commands/cluster-nodes # string / bytes 

498 @see: https://redis.io/commands/cluster-replicas # list of string / bytes 

499 """ 

500 if isinstance(response, (str, bytes)): 

501 response = response.splitlines() 

502 return dict(_parse_node_line(str_if_bytes(node)) for node in response) 

503 

504 

505def parse_geosearch_generic(response, **options): 

506 """ 

507 Parse the response of 'GEOSEARCH', GEORADIUS' and 'GEORADIUSBYMEMBER' 

508 commands according to 'withdist', 'withhash' and 'withcoord' labels. 

509 """ 

510 try: 

511 if options["store"] or options["store_dist"]: 

512 # `store` and `store_dist` cant be combined 

513 # with other command arguments. 

514 # relevant to 'GEORADIUS' and 'GEORADIUSBYMEMBER' 

515 return response 

516 except KeyError: # it means the command was sent via execute_command 

517 return response 

518 

519 if not isinstance(response, list): 

520 response_list = [response] 

521 else: 

522 response_list = response 

523 

524 if not options["withdist"] and not options["withcoord"] and not options["withhash"]: 

525 # just a bunch of places 

526 return response_list 

527 

528 cast = { 

529 "withdist": float, 

530 "withcoord": lambda ll: (float(ll[0]), float(ll[1])), 

531 "withhash": int, 

532 } 

533 

534 # zip all output results with each casting function to get 

535 # the properly native Python value. 

536 f = [lambda x: x] 

537 f += [cast[o] for o in ["withdist", "withhash", "withcoord"] if options[o]] 

538 return [list(map(lambda fv: fv[0](fv[1]), zip(f, r))) for r in response_list] 

539 

540 

541def parse_command(response, **options): 

542 commands = {} 

543 for command in response: 

544 cmd_dict = {} 

545 cmd_name = str_if_bytes(command[0]) 

546 cmd_dict["name"] = cmd_name 

547 cmd_dict["arity"] = int(command[1]) 

548 cmd_dict["flags"] = [str_if_bytes(flag) for flag in command[2]] 

549 cmd_dict["first_key_pos"] = command[3] 

550 cmd_dict["last_key_pos"] = command[4] 

551 cmd_dict["step_count"] = command[5] 

552 if len(command) > 7: 

553 cmd_dict["tips"] = command[7] 

554 cmd_dict["key_specifications"] = command[8] 

555 cmd_dict["subcommands"] = command[9] 

556 commands[cmd_name] = cmd_dict 

557 return commands 

558 

559 

560def parse_command_resp3(response, **options): 

561 commands = {} 

562 for command in response: 

563 cmd_dict = {} 

564 cmd_name = str_if_bytes(command[0]) 

565 cmd_dict["name"] = cmd_name 

566 cmd_dict["arity"] = command[1] 

567 cmd_dict["flags"] = {str_if_bytes(flag) for flag in command[2]} 

568 cmd_dict["first_key_pos"] = command[3] 

569 cmd_dict["last_key_pos"] = command[4] 

570 cmd_dict["step_count"] = command[5] 

571 cmd_dict["acl_categories"] = command[6] 

572 if len(command) > 7: 

573 cmd_dict["tips"] = command[7] 

574 cmd_dict["key_specifications"] = command[8] 

575 cmd_dict["subcommands"] = command[9] 

576 

577 commands[cmd_name] = cmd_dict 

578 return commands 

579 

580 

581def parse_pubsub_numsub(response, **options): 

582 return list(zip(response[0::2], response[1::2])) 

583 

584 

585def parse_client_kill(response, **options): 

586 if isinstance(response, int): 

587 return response 

588 return str_if_bytes(response) == "OK" 

589 

590 

591def parse_acl_getuser(response, **options): 

592 if response is None: 

593 return None 

594 if isinstance(response, list): 

595 data = pairs_to_dict(response, decode_keys=True) 

596 else: 

597 data = {str_if_bytes(key): value for key, value in response.items()} 

598 

599 # convert everything but user-defined data in 'keys' to native strings 

600 data["flags"] = list(map(str_if_bytes, data["flags"])) 

601 data["passwords"] = list(map(str_if_bytes, data["passwords"])) 

602 data["commands"] = str_if_bytes(data["commands"]) 

603 if isinstance(data["keys"], str) or isinstance(data["keys"], bytes): 

604 data["keys"] = list(str_if_bytes(data["keys"]).split(" ")) 

605 if data["keys"] == [""]: 

606 data["keys"] = [] 

607 if "channels" in data: 

608 if isinstance(data["channels"], str) or isinstance(data["channels"], bytes): 

609 data["channels"] = list(str_if_bytes(data["channels"]).split(" ")) 

610 if data["channels"] == [""]: 

611 data["channels"] = [] 

612 if "selectors" in data: 

613 if data["selectors"] != [] and isinstance(data["selectors"][0], list): 

614 data["selectors"] = [ 

615 list(map(str_if_bytes, selector)) for selector in data["selectors"] 

616 ] 

617 elif data["selectors"] != []: 

618 data["selectors"] = [ 

619 {str_if_bytes(k): str_if_bytes(v) for k, v in selector.items()} 

620 for selector in data["selectors"] 

621 ] 

622 

623 # split 'commands' into separate 'categories' and 'commands' lists 

624 commands, categories = [], [] 

625 for command in data["commands"].split(" "): 

626 categories.append(command) if "@" in command else commands.append(command) 

627 

628 data["commands"] = commands 

629 data["categories"] = categories 

630 data["enabled"] = "on" in data["flags"] 

631 return data 

632 

633 

634def parse_acl_log(response, **options): 

635 if response is None: 

636 return None 

637 if isinstance(response, list): 

638 data = [] 

639 for log in response: 

640 log_data = pairs_to_dict(log, True, True) 

641 client_info = log_data.get("client-info", "") 

642 log_data["client-info"] = parse_client_info(client_info) 

643 

644 # float() is lossy comparing to the "double" in C 

645 log_data["age-seconds"] = float(log_data["age-seconds"]) 

646 data.append(log_data) 

647 else: 

648 data = bool_ok(response) 

649 return data 

650 

651 

652def parse_client_info(value): 

653 """ 

654 Parsing client-info in ACL Log in following format. 

655 "key1=value1 key2=value2 key3=value3" 

656 """ 

657 client_info = {} 

658 for info in str_if_bytes(value).strip().split(): 

659 key, value = info.split("=") 

660 client_info[key] = value 

661 

662 # Those fields are defined as int in networking.c 

663 for int_key in { 

664 "id", 

665 "age", 

666 "idle", 

667 "db", 

668 "sub", 

669 "psub", 

670 "multi", 

671 "qbuf", 

672 "qbuf-free", 

673 "obl", 

674 "argv-mem", 

675 "oll", 

676 "omem", 

677 "tot-mem", 

678 }: 

679 client_info[int_key] = int(client_info[int_key]) 

680 return client_info 

681 

682 

683def parse_set_result(response, **options): 

684 """ 

685 Handle SET result since GET argument is available since Redis 6.2. 

686 Parsing SET result into: 

687 - BOOL 

688 - String when GET argument is used 

689 """ 

690 if options.get("get"): 

691 # Redis will return a getCommand result. 

692 # See `setGenericCommand` in t_string.c 

693 return response 

694 return response and str_if_bytes(response) == "OK" 

695 

696 

697def string_keys_to_dict(key_string, callback): 

698 return dict.fromkeys(key_string.split(), callback) 

699 

700 

701_RedisCallbacks = { 

702 **string_keys_to_dict( 

703 "AUTH COPY EXPIRE EXPIREAT HEXISTS HMSET MOVE MSETNX PERSIST PSETEX " 

704 "PEXPIRE PEXPIREAT RENAMENX SETEX SETNX SMOVE", 

705 bool, 

706 ), 

707 **string_keys_to_dict("HINCRBYFLOAT INCRBYFLOAT", float), 

708 **string_keys_to_dict( 

709 "ASKING FLUSHALL FLUSHDB LSET LTRIM MSET PFMERGE READONLY READWRITE " 

710 "RENAME SAVE SELECT SHUTDOWN SLAVEOF SWAPDB WATCH UNWATCH", 

711 bool_ok, 

712 ), 

713 **string_keys_to_dict("XREAD XREADGROUP", parse_xread), 

714 **string_keys_to_dict( 

715 "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH", 

716 parse_geosearch_generic, 

717 ), 

718 **string_keys_to_dict("XRANGE XREVRANGE", parse_stream_list), 

719 "ACL GETUSER": parse_acl_getuser, 

720 "ACL LOAD": bool_ok, 

721 "ACL LOG": parse_acl_log, 

722 "ACL SETUSER": bool_ok, 

723 "ACL SAVE": bool_ok, 

724 "CLIENT INFO": parse_client_info, 

725 "CLIENT KILL": parse_client_kill, 

726 "CLIENT LIST": parse_client_list, 

727 "CLIENT PAUSE": bool_ok, 

728 "CLIENT SETINFO": bool_ok, 

729 "CLIENT SETNAME": bool_ok, 

730 "CLIENT UNBLOCK": bool, 

731 "CLUSTER ADDSLOTS": bool_ok, 

732 "CLUSTER ADDSLOTSRANGE": bool_ok, 

733 "CLUSTER DELSLOTS": bool_ok, 

734 "CLUSTER DELSLOTSRANGE": bool_ok, 

735 "CLUSTER FAILOVER": bool_ok, 

736 "CLUSTER FORGET": bool_ok, 

737 "CLUSTER INFO": parse_cluster_info, 

738 "CLUSTER MEET": bool_ok, 

739 "CLUSTER NODES": parse_cluster_nodes, 

740 "CLUSTER REPLICAS": parse_cluster_nodes, 

741 "CLUSTER REPLICATE": bool_ok, 

742 "CLUSTER RESET": bool_ok, 

743 "CLUSTER SAVECONFIG": bool_ok, 

744 "CLUSTER SET-CONFIG-EPOCH": bool_ok, 

745 "CLUSTER SETSLOT": bool_ok, 

746 "CLUSTER SLAVES": parse_cluster_nodes, 

747 "COMMAND": parse_command, 

748 "CONFIG RESETSTAT": bool_ok, 

749 "CONFIG SET": bool_ok, 

750 "FUNCTION DELETE": bool_ok, 

751 "FUNCTION FLUSH": bool_ok, 

752 "FUNCTION RESTORE": bool_ok, 

753 "GEODIST": float_or_none, 

754 "HSCAN": parse_hscan, 

755 "INFO": parse_info, 

756 "LASTSAVE": timestamp_to_datetime, 

757 "MEMORY PURGE": bool_ok, 

758 "MODULE LOAD": bool, 

759 "MODULE UNLOAD": bool, 

760 "PING": lambda r: str_if_bytes(r) == "PONG", 

761 "PUBSUB NUMSUB": parse_pubsub_numsub, 

762 "PUBSUB SHARDNUMSUB": parse_pubsub_numsub, 

763 "QUIT": bool_ok, 

764 "SET": parse_set_result, 

765 "SCAN": parse_scan, 

766 "SCRIPT EXISTS": lambda r: list(map(bool, r)), 

767 "SCRIPT FLUSH": bool_ok, 

768 "SCRIPT KILL": bool_ok, 

769 "SCRIPT LOAD": str_if_bytes, 

770 "SENTINEL CKQUORUM": bool_ok, 

771 "SENTINEL FAILOVER": bool_ok, 

772 "SENTINEL FLUSHCONFIG": bool_ok, 

773 "SENTINEL GET-MASTER-ADDR-BY-NAME": parse_sentinel_get_master, 

774 "SENTINEL MONITOR": bool_ok, 

775 "SENTINEL RESET": bool_ok, 

776 "SENTINEL REMOVE": bool_ok, 

777 "SENTINEL SET": bool_ok, 

778 "SLOWLOG GET": parse_slowlog_get, 

779 "SLOWLOG RESET": bool_ok, 

780 "SORT": sort_return_tuples, 

781 "SSCAN": parse_scan, 

782 "TIME": lambda x: (int(x[0]), int(x[1])), 

783 "XAUTOCLAIM": parse_xautoclaim, 

784 "XCLAIM": parse_xclaim, 

785 "XGROUP CREATE": bool_ok, 

786 "XGROUP DESTROY": bool, 

787 "XGROUP SETID": bool_ok, 

788 "XINFO STREAM": parse_xinfo_stream, 

789 "XPENDING": parse_xpending, 

790 "ZSCAN": parse_zscan, 

791} 

792 

793 

794_RedisCallbacksRESP2 = { 

795 **string_keys_to_dict( 

796 "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set() 

797 ), 

798 **string_keys_to_dict( 

799 "ZDIFF ZINTER ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYSCORE ZRANK ZREVRANGE " 

800 "ZREVRANGEBYSCORE ZREVRANK ZUNION", 

801 zset_score_pairs, 

802 ), 

803 **string_keys_to_dict("ZINCRBY ZSCORE", float_or_none), 

804 **string_keys_to_dict("BGREWRITEAOF BGSAVE", lambda r: True), 

805 **string_keys_to_dict("BLPOP BRPOP", lambda r: r and tuple(r) or None), 

806 **string_keys_to_dict( 

807 "BZPOPMAX BZPOPMIN", lambda r: r and (r[0], r[1], float(r[2])) or None 

808 ), 

809 "ACL CAT": lambda r: list(map(str_if_bytes, r)), 

810 "ACL GENPASS": str_if_bytes, 

811 "ACL HELP": lambda r: list(map(str_if_bytes, r)), 

812 "ACL LIST": lambda r: list(map(str_if_bytes, r)), 

813 "ACL USERS": lambda r: list(map(str_if_bytes, r)), 

814 "ACL WHOAMI": str_if_bytes, 

815 "CLIENT GETNAME": str_if_bytes, 

816 "CLIENT TRACKINGINFO": lambda r: list(map(str_if_bytes, r)), 

817 "CLUSTER GETKEYSINSLOT": lambda r: list(map(str_if_bytes, r)), 

818 "COMMAND GETKEYS": lambda r: list(map(str_if_bytes, r)), 

819 "CONFIG GET": parse_config_get, 

820 "DEBUG OBJECT": parse_debug_object, 

821 "GEOHASH": lambda r: list(map(str_if_bytes, r)), 

822 "GEOPOS": lambda r: list( 

823 map(lambda ll: (float(ll[0]), float(ll[1])) if ll is not None else None, r) 

824 ), 

825 "HGETALL": lambda r: r and pairs_to_dict(r) or {}, 

826 "MEMORY STATS": parse_memory_stats, 

827 "MODULE LIST": lambda r: [pairs_to_dict(m) for m in r], 

828 "RESET": str_if_bytes, 

829 "SENTINEL MASTER": parse_sentinel_master, 

830 "SENTINEL MASTERS": parse_sentinel_masters, 

831 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels, 

832 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels, 

833 "STRALGO": parse_stralgo, 

834 "XINFO CONSUMERS": parse_list_of_dicts, 

835 "XINFO GROUPS": parse_list_of_dicts, 

836 "ZADD": parse_zadd, 

837 "ZMSCORE": parse_zmscore, 

838} 

839 

840 

841_RedisCallbacksRESP3 = { 

842 **string_keys_to_dict( 

843 "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set() 

844 ), 

845 **string_keys_to_dict( 

846 "ZRANGE ZINTER ZPOPMAX ZPOPMIN ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE " 

847 "ZUNION HGETALL XREADGROUP", 

848 lambda r, **kwargs: r, 

849 ), 

850 **string_keys_to_dict("XREAD XREADGROUP", parse_xread_resp3), 

851 "ACL LOG": lambda r: ( 

852 [ 

853 {str_if_bytes(key): str_if_bytes(value) for key, value in x.items()} 

854 for x in r 

855 ] 

856 if isinstance(r, list) 

857 else bool_ok(r) 

858 ), 

859 "COMMAND": parse_command_resp3, 

860 "CONFIG GET": lambda r: { 

861 str_if_bytes(key) if key is not None else None: ( 

862 str_if_bytes(value) if value is not None else None 

863 ) 

864 for key, value in r.items() 

865 }, 

866 "MEMORY STATS": lambda r: {str_if_bytes(key): value for key, value in r.items()}, 

867 "SENTINEL MASTER": parse_sentinel_state_resp3, 

868 "SENTINEL MASTERS": parse_sentinel_masters_resp3, 

869 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_resp3, 

870 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_resp3, 

871 "STRALGO": lambda r, **options: ( 

872 {str_if_bytes(key): str_if_bytes(value) for key, value in r.items()} 

873 if isinstance(r, dict) 

874 else str_if_bytes(r) 

875 ), 

876 "XINFO CONSUMERS": lambda r: [ 

877 {str_if_bytes(key): value for key, value in x.items()} for x in r 

878 ], 

879 "XINFO GROUPS": lambda r: [ 

880 {str_if_bytes(key): value for key, value in d.items()} for d in r 

881 ], 

882}