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

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

55 

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) 

65 

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) 

75 

76 return info 

77 

78 

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 

88 

89 

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} 

115 

116 

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 

131 

132 

133def parse_sentinel_master(response): 

134 return parse_sentinel_state(map(str_if_bytes, response)) 

135 

136 

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 

148 

149 

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 

156 

157 

158def parse_sentinel_masters_resp3(response): 

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

160 

161 

162def parse_sentinel_slaves_and_sentinels(response): 

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

164 

165 

166def parse_sentinel_slaves_and_sentinels_resp3(response): 

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

168 

169 

170def parse_sentinel_get_master(response): 

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

172 

173 

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

191 

192 

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 

206 

207 

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

218 

219 

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

229 

230 

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 

241 

242 

243def pairs_to_dict_with_str_keys(response): 

244 return pairs_to_dict(response, decode_keys=True) 

245 

246 

247def parse_list_of_dicts(response): 

248 return list(map(pairs_to_dict_with_str_keys, response)) 

249 

250 

251def parse_xclaim(response, **options): 

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

253 return response 

254 return parse_stream_list(response) 

255 

256 

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 

262 

263 

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 

288 

289 

290def parse_xread(response): 

291 if response is None: 

292 return [] 

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

294 

295 

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

300 

301 

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 } 

312 

313 

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] 

317 

318 

319def float_or_none(response): 

320 if response is None: 

321 return None 

322 return float(response) 

323 

324 

325def bool_ok(response, **options): 

326 return str_if_bytes(response) == "OK" 

327 

328 

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) 

335 

336 

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 

343 

344 

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 {} 

348 

349 

350def parse_scan(response, **options): 

351 cursor, r = response 

352 return int(cursor), r 

353 

354 

355def parse_hscan(response, **options): 

356 cursor, r = response 

357 return int(cursor), r and pairs_to_dict(r) or {} 

358 

359 

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

365 

366 

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] 

370 

371 

372def parse_slowlog_get(response, **options): 

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

374 

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 

390 

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

392 

393 

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) 

421 

422 

423def parse_cluster_info(response, **options): 

424 response = str_if_bytes(response) 

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

426 

427 

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 

447 

448 

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) 

465 

466 return slots, migrations 

467 

468 

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) 

477 

478 

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 

492 

493 if type(response) != list: 

494 response_list = [response] 

495 else: 

496 response_list = response 

497 

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

499 # just a bunch of places 

500 return response_list 

501 

502 cast = { 

503 "withdist": float, 

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

505 "withhash": int, 

506 } 

507 

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] 

513 

514 

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 

532 

533 

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] 

550 

551 commands[cmd_name] = cmd_dict 

552 return commands 

553 

554 

555def parse_pubsub_numsub(response, **options): 

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

557 

558 

559def parse_client_kill(response, **options): 

560 if isinstance(response, int): 

561 return response 

562 return str_if_bytes(response) == "OK" 

563 

564 

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

572 

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 ] 

596 

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) 

601 

602 data["commands"] = commands 

603 data["categories"] = categories 

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

605 return data 

606 

607 

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) 

617 

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 

624 

625 

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 

635 

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 

655 

656 

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" 

669 

670 

671def string_keys_to_dict(key_string, callback): 

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

673 

674 

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} 

766 

767 

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} 

813 

814 

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}