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

419 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, **options): 

141 return parse_sentinel_state(map(str_if_bytes, response)) 

142 

143 

144def parse_sentinel_state_resp3(response, **options): 

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, **options): 

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, **options): 

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

167 

168 

169def parse_sentinel_slaves_and_sentinels(response, **options): 

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

171 

172 

173def parse_sentinel_slaves_and_sentinels_resp3(response, **options): 

174 return [parse_sentinel_state_resp3(item, **options) for item in response] 

175 

176 

177def parse_sentinel_get_master(response, **options): 

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 zset_score_for_rank(response, **options): 

228 """ 

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

230 a [value, score] pair 

231 """ 

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

233 return response 

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

235 return [response[0], score_cast_func(response[1])] 

236 

237 

238def zset_score_pairs_resp3(response, **options): 

239 """ 

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

241 a list of [value, score] pairs 

242 """ 

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

244 return response 

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

246 return [[name, score_cast_func(val)] for name, val in response] 

247 

248 

249def zset_score_for_rank_resp3(response, **options): 

250 """ 

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

252 a [value, score] pair 

253 """ 

254 if not response or not options.get("withscore"): 

255 return response 

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

257 return [response[0], score_cast_func(response[1])] 

258 

259 

260def sort_return_tuples(response, **options): 

261 """ 

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

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

264 """ 

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

266 return response 

267 n = options["groups"] 

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

269 

270 

271def parse_stream_list(response, **options): 

272 if response is None: 

273 return None 

274 data = [] 

275 for r in response: 

276 if r is not None: 

277 if "claim_min_idle_time" in options: 

278 data.append((r[0], pairs_to_dict(r[1]), *r[2:])) 

279 else: 

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

281 else: 

282 data.append((None, None)) 

283 return data 

284 

285 

286def pairs_to_dict_with_str_keys(response): 

287 return pairs_to_dict(response, decode_keys=True) 

288 

289 

290def parse_list_of_dicts(response): 

291 return list(map(pairs_to_dict_with_str_keys, response)) 

292 

293 

294def parse_xclaim(response, **options): 

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

296 return response 

297 return parse_stream_list(response) 

298 

299 

300def parse_xautoclaim(response, **options): 

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

302 return response[1] 

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

304 return response 

305 

306 

307def parse_xinfo_stream(response, **options): 

308 if isinstance(response, list): 

309 data = pairs_to_dict(response, decode_keys=True) 

310 else: 

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

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

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

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

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

316 last = data["last-entry"] 

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

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

319 else: 

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

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

322 data["groups"] = [ 

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

324 ] 

325 for g in data["groups"]: 

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

327 g["consumers"] = [ 

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

329 ] 

330 else: 

331 data["groups"] = [ 

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

333 for group in data["groups"] 

334 ] 

335 return data 

336 

337 

338def parse_xread(response, **options): 

339 if response is None: 

340 return [] 

341 return [[r[0], parse_stream_list(r[1], **options)] for r in response] 

342 

343 

344def parse_xread_resp3(response, **options): 

345 if response is None: 

346 return {} 

347 return { 

348 key: [parse_stream_list(value, **options)] for key, value in response.items() 

349 } 

350 

351 

352def parse_xpending(response, **options): 

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

354 return parse_xpending_range(response) 

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

356 return { 

357 "pending": response[0], 

358 "min": response[1], 

359 "max": response[2], 

360 "consumers": consumers, 

361 } 

362 

363 

364def parse_xpending_range(response): 

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

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

367 

368 

369def float_or_none(response): 

370 if response is None: 

371 return None 

372 return float(response) 

373 

374 

375def bool_ok(response, **options): 

376 return str_if_bytes(response) == "OK" 

377 

378 

379def parse_zadd(response, **options): 

380 if response is None: 

381 return None 

382 if options.get("as_score"): 

383 return float(response) 

384 return int(response) 

385 

386 

387def parse_client_list(response, **options): 

388 clients = [] 

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

390 client_dict = {} 

391 tokens = c.split(" ") 

392 last_key = None 

393 for token in tokens: 

394 if "=" in token: 

395 # Values might contain '=' 

396 key, value = token.split("=", 1) 

397 client_dict[key] = value 

398 last_key = key 

399 else: 

400 # Values may include spaces. For instance, when running Redis via a Unix socket — such as 

401 # "/tmp/redis sock/redis.sock" — the addr or laddr field will include a space. 

402 client_dict[last_key] += " " + token 

403 

404 if client_dict: 

405 clients.append(client_dict) 

406 return clients 

407 

408 

409def parse_config_get(response, **options): 

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

411 return response and pairs_to_dict(response) or {} 

412 

413 

414def parse_scan(response, **options): 

415 cursor, r = response 

416 return int(cursor), r 

417 

418 

419def parse_hscan(response, **options): 

420 cursor, r = response 

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

422 if no_values: 

423 payload = r or [] 

424 else: 

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

426 return int(cursor), payload 

427 

428 

429def parse_zscan(response, **options): 

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

431 cursor, r = response 

432 it = iter(r) 

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

434 

435 

436def parse_zmscore(response, **options): 

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

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

439 

440 

441def parse_slowlog_get(response, **options): 

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

443 

444 def parse_item(item): 

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

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

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

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

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

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

451 

452 # These fields are optional, depends on environment. 

453 if len(item) >= 6: 

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

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

456 else: 

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

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

459 

460 # These fields are optional, depends on environment. 

461 if len(item) >= 7: 

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

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

464 

465 return result 

466 

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

468 

469 

470def parse_stralgo(response, **options): 

471 """ 

472 Parse the response from `STRALGO` command. 

473 Without modifiers the returned value is string. 

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

475 (i.e integer). 

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

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

478 offset for each string, where there are matches. 

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

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

481 """ 

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

483 return int(response) 

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

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

486 matches = [ 

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

488 for match in response[1] 

489 ] 

490 else: 

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

492 return { 

493 str_if_bytes(response[0]): matches, 

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

495 } 

496 return str_if_bytes(response) 

497 

498 

499def parse_cluster_info(response, **options): 

500 response = str_if_bytes(response) 

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

502 

503 

504def _parse_node_line(line): 

505 line_items = line.split(" ") 

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

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

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

509 node_dict = { 

510 "node_id": node_id, 

511 "hostname": hostname, 

512 "flags": flags, 

513 "master_id": master_id, 

514 "last_ping_sent": ping, 

515 "last_pong_rcvd": pong, 

516 "epoch": epoch, 

517 "slots": [], 

518 "migrations": [], 

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

520 } 

521 if len(line_items) >= 9: 

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

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

524 return ip, node_dict 

525 

526 

527def _parse_slots(slot_ranges): 

528 slots, migrations = [], [] 

529 for s_range in slot_ranges: 

530 if "->-" in s_range: 

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

532 migrations.append( 

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

534 ) 

535 elif "-<-" in s_range: 

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

537 migrations.append( 

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

539 ) 

540 else: 

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

542 slots.append(s_range) 

543 

544 return slots, migrations 

545 

546 

547def parse_cluster_nodes(response, **options): 

548 """ 

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

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

551 """ 

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

553 response = response.splitlines() 

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

555 

556 

557def parse_geosearch_generic(response, **options): 

558 """ 

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

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

561 """ 

562 try: 

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

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

565 # with other command arguments. 

566 # relevant to 'GEORADIUS' and 'GEORADIUSBYMEMBER' 

567 return response 

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

569 return response 

570 

571 if not isinstance(response, list): 

572 response_list = [response] 

573 else: 

574 response_list = response 

575 

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

577 # just a bunch of places 

578 return response_list 

579 

580 cast = { 

581 "withdist": float, 

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

583 "withhash": int, 

584 } 

585 

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

587 # the properly native Python value. 

588 f = [lambda x: x] 

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

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

591 

592 

593def parse_command(response, **options): 

594 commands = {} 

595 for command in response: 

596 cmd_dict = {} 

597 cmd_name = str_if_bytes(command[0]) 

598 cmd_dict["name"] = cmd_name 

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

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

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

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

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

604 if len(command) > 7: 

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

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

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

608 commands[cmd_name] = cmd_dict 

609 return commands 

610 

611 

612def parse_command_resp3(response, **options): 

613 commands = {} 

614 for command in response: 

615 cmd_dict = {} 

616 cmd_name = str_if_bytes(command[0]) 

617 cmd_dict["name"] = cmd_name 

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

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

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

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

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

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

624 if len(command) > 7: 

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

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

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

628 

629 commands[cmd_name] = cmd_dict 

630 return commands 

631 

632 

633def parse_pubsub_numsub(response, **options): 

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

635 

636 

637def parse_client_kill(response, **options): 

638 if isinstance(response, int): 

639 return response 

640 return str_if_bytes(response) == "OK" 

641 

642 

643def parse_acl_getuser(response, **options): 

644 if response is None: 

645 return None 

646 if isinstance(response, list): 

647 data = pairs_to_dict(response, decode_keys=True) 

648 else: 

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

650 

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

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

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

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

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

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

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

658 data["keys"] = [] 

659 if "channels" in data: 

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

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

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

663 data["channels"] = [] 

664 if "selectors" in data: 

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

666 data["selectors"] = [ 

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

668 ] 

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

670 data["selectors"] = [ 

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

672 for selector in data["selectors"] 

673 ] 

674 

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

676 commands, categories = [], [] 

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

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

679 

680 data["commands"] = commands 

681 data["categories"] = categories 

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

683 return data 

684 

685 

686def parse_acl_log(response, **options): 

687 if response is None: 

688 return None 

689 if isinstance(response, list): 

690 data = [] 

691 for log in response: 

692 log_data = pairs_to_dict(log, True, True) 

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

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

695 

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

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

698 data.append(log_data) 

699 else: 

700 data = bool_ok(response) 

701 return data 

702 

703 

704def parse_client_info(value): 

705 """ 

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

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

708 """ 

709 client_info = {} 

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

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

712 client_info[key] = value 

713 

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

715 for int_key in { 

716 "id", 

717 "age", 

718 "idle", 

719 "db", 

720 "sub", 

721 "psub", 

722 "multi", 

723 "qbuf", 

724 "qbuf-free", 

725 "obl", 

726 "argv-mem", 

727 "oll", 

728 "omem", 

729 "tot-mem", 

730 }: 

731 if int_key in client_info: 

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

733 return client_info 

734 

735 

736def parse_set_result(response, **options): 

737 """ 

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

739 Parsing SET result into: 

740 - BOOL 

741 - String when GET argument is used 

742 """ 

743 if options.get("get"): 

744 # Redis will return a getCommand result. 

745 # See `setGenericCommand` in t_string.c 

746 return response 

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

748 

749 

750def string_keys_to_dict(key_string, callback): 

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

752 

753 

754_RedisCallbacks = { 

755 **string_keys_to_dict( 

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

757 "PEXPIRE PEXPIREAT RENAMENX SETEX SETNX SMOVE", 

758 bool, 

759 ), 

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

761 **string_keys_to_dict( 

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

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

764 bool_ok, 

765 ), 

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

767 **string_keys_to_dict( 

768 "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH", 

769 parse_geosearch_generic, 

770 ), 

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

772 "ACL GETUSER": parse_acl_getuser, 

773 "ACL LOAD": bool_ok, 

774 "ACL LOG": parse_acl_log, 

775 "ACL SETUSER": bool_ok, 

776 "ACL SAVE": bool_ok, 

777 "CLIENT INFO": parse_client_info, 

778 "CLIENT KILL": parse_client_kill, 

779 "CLIENT LIST": parse_client_list, 

780 "CLIENT PAUSE": bool_ok, 

781 "CLIENT SETINFO": bool_ok, 

782 "CLIENT SETNAME": bool_ok, 

783 "CLIENT UNBLOCK": bool, 

784 "CLUSTER ADDSLOTS": bool_ok, 

785 "CLUSTER ADDSLOTSRANGE": bool_ok, 

786 "CLUSTER DELSLOTS": bool_ok, 

787 "CLUSTER DELSLOTSRANGE": bool_ok, 

788 "CLUSTER FAILOVER": bool_ok, 

789 "CLUSTER FORGET": bool_ok, 

790 "CLUSTER INFO": parse_cluster_info, 

791 "CLUSTER MEET": bool_ok, 

792 "CLUSTER NODES": parse_cluster_nodes, 

793 "CLUSTER REPLICAS": parse_cluster_nodes, 

794 "CLUSTER REPLICATE": bool_ok, 

795 "CLUSTER RESET": bool_ok, 

796 "CLUSTER SAVECONFIG": bool_ok, 

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

798 "CLUSTER SETSLOT": bool_ok, 

799 "CLUSTER SLAVES": parse_cluster_nodes, 

800 "COMMAND": parse_command, 

801 "CONFIG RESETSTAT": bool_ok, 

802 "CONFIG SET": bool_ok, 

803 "FUNCTION DELETE": bool_ok, 

804 "FUNCTION FLUSH": bool_ok, 

805 "FUNCTION RESTORE": bool_ok, 

806 "GEODIST": float_or_none, 

807 "HSCAN": parse_hscan, 

808 "INFO": parse_info, 

809 "LASTSAVE": timestamp_to_datetime, 

810 "MEMORY PURGE": bool_ok, 

811 "MODULE LOAD": bool, 

812 "MODULE UNLOAD": bool, 

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

814 "PUBSUB NUMSUB": parse_pubsub_numsub, 

815 "PUBSUB SHARDNUMSUB": parse_pubsub_numsub, 

816 "QUIT": bool_ok, 

817 "SET": parse_set_result, 

818 "SCAN": parse_scan, 

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

820 "SCRIPT FLUSH": bool_ok, 

821 "SCRIPT KILL": bool_ok, 

822 "SCRIPT LOAD": str_if_bytes, 

823 "SENTINEL CKQUORUM": bool_ok, 

824 "SENTINEL FAILOVER": bool_ok, 

825 "SENTINEL FLUSHCONFIG": bool_ok, 

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

827 "SENTINEL MONITOR": bool_ok, 

828 "SENTINEL RESET": bool_ok, 

829 "SENTINEL REMOVE": bool_ok, 

830 "SENTINEL SET": bool_ok, 

831 "SLOWLOG GET": parse_slowlog_get, 

832 "SLOWLOG RESET": bool_ok, 

833 "SORT": sort_return_tuples, 

834 "SSCAN": parse_scan, 

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

836 "XAUTOCLAIM": parse_xautoclaim, 

837 "XCLAIM": parse_xclaim, 

838 "XGROUP CREATE": bool_ok, 

839 "XGROUP DESTROY": bool, 

840 "XGROUP SETID": bool_ok, 

841 "XINFO STREAM": parse_xinfo_stream, 

842 "XPENDING": parse_xpending, 

843 "ZSCAN": parse_zscan, 

844} 

845 

846 

847_RedisCallbacksRESP2 = { 

848 **string_keys_to_dict( 

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

850 ), 

851 **string_keys_to_dict( 

852 "ZDIFF ZINTER ZPOPMAX ZPOPMIN ZRANGE ZRANGEBYSCORE ZREVRANGE " 

853 "ZREVRANGEBYSCORE ZUNION", 

854 zset_score_pairs, 

855 ), 

856 **string_keys_to_dict( 

857 "ZREVRANK ZRANK", 

858 zset_score_for_rank, 

859 ), 

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

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

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

863 **string_keys_to_dict( 

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

865 ), 

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

867 "ACL GENPASS": str_if_bytes, 

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

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

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

871 "ACL WHOAMI": str_if_bytes, 

872 "CLIENT GETNAME": str_if_bytes, 

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

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

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

876 "CONFIG GET": parse_config_get, 

877 "DEBUG OBJECT": parse_debug_object, 

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

879 "GEOPOS": lambda r: list( 

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

881 ), 

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

883 "MEMORY STATS": parse_memory_stats, 

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

885 "RESET": str_if_bytes, 

886 "SENTINEL MASTER": parse_sentinel_master, 

887 "SENTINEL MASTERS": parse_sentinel_masters, 

888 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels, 

889 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels, 

890 "STRALGO": parse_stralgo, 

891 "XINFO CONSUMERS": parse_list_of_dicts, 

892 "XINFO GROUPS": parse_list_of_dicts, 

893 "ZADD": parse_zadd, 

894 "ZMSCORE": parse_zmscore, 

895} 

896 

897 

898_RedisCallbacksRESP3 = { 

899 **string_keys_to_dict( 

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

901 ), 

902 **string_keys_to_dict( 

903 "ZRANGE ZINTER ZPOPMAX ZPOPMIN HGETALL XREADGROUP", 

904 lambda r, **kwargs: r, 

905 ), 

906 **string_keys_to_dict( 

907 "ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION", 

908 zset_score_pairs_resp3, 

909 ), 

910 **string_keys_to_dict( 

911 "ZREVRANK ZRANK", 

912 zset_score_for_rank_resp3, 

913 ), 

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

915 "ACL LOG": lambda r: ( 

916 [ 

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

918 for x in r 

919 ] 

920 if isinstance(r, list) 

921 else bool_ok(r) 

922 ), 

923 "COMMAND": parse_command_resp3, 

924 "CONFIG GET": lambda r: { 

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

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

927 ) 

928 for key, value in r.items() 

929 }, 

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

931 "SENTINEL MASTER": parse_sentinel_state_resp3, 

932 "SENTINEL MASTERS": parse_sentinel_masters_resp3, 

933 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_resp3, 

934 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_resp3, 

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

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

937 if isinstance(r, dict) 

938 else str_if_bytes(r) 

939 ), 

940 "XINFO CONSUMERS": lambda r: [ 

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

942 ], 

943 "XINFO GROUPS": lambda r: [ 

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

945 ], 

946}