1"""Response-callback dictionaries and the protocol/legacy selector.
2
3This module is the single source of truth for the mapping between Redis
4command names and the Python-side callbacks that post-process raw parser
5output into user-facing values.
6
7Six dictionaries are defined:
8
9* ``_RedisCallbacks`` — entries that produce the same Python value
10 regardless of wire protocol or legacy-response selection.
11* ``_RedisCallbacksRESP2`` — RESP2 wire, legacy Python shapes.
12* ``_RedisCallbacksRESP3`` — RESP3 wire, Python shapes from the previous
13 RESP3 callbacks.
14* ``_RedisCallbacksRESP2Unified`` — RESP2 wire, unified Python shapes
15 (``legacy_responses=False``).
16* ``_RedisCallbacksRESP3Unified`` — RESP3 wire, unified Python shapes
17 (``legacy_responses=False``).
18* ``_RedisCallbacksRESP3toRESP2Legacy`` — RESP3 wire converted back to the
19 legacy RESP2 Python shapes for users who keep ``legacy_responses=True``
20 on a RESP3 connection.
21
22``get_response_callbacks`` merges ``_RedisCallbacks`` with the appropriate
23protocol-specific overlay. Callers wrap the returned dict in
24``CaseInsensitiveDict``.
25"""
26
27from typing import Any, Callable, Optional
28
29from redis.himport import (
30 HIMPORT_DISCARD,
31 HIMPORT_DISCARDALL,
32 HIMPORT_PREPARE,
33 HIMPORT_SET,
34)
35from redis.utils import str_if_bytes
36
37from .helpers import (
38 bool_ok,
39 bzpop_score_resp3_to_resp2_legacy,
40 bzpop_score_unified,
41 float_or_none,
42 hrandfield_resp3_to_resp2_legacy,
43 hrandfield_unified,
44 pairs_to_dict,
45 parse_acl_getuser,
46 parse_acl_getuser_resp3_to_resp2_legacy,
47 parse_acl_getuser_unified,
48 parse_acl_log,
49 parse_acl_log_resp3_to_resp2_legacy,
50 parse_acl_log_resp3_unified,
51 parse_arinfo,
52 parse_client_info,
53 parse_client_kill,
54 parse_client_list,
55 parse_client_trackinginfo_resp3_to_resp2_legacy,
56 parse_client_trackinginfo_unified,
57 parse_cluster_info,
58 parse_cluster_links_resp3_to_resp2_legacy,
59 parse_cluster_links_unified,
60 parse_cluster_nodes,
61 parse_command,
62 parse_command_resp3,
63 parse_command_unified,
64 parse_config_get,
65 parse_config_get_resp3_to_resp2_legacy,
66 parse_debug_object,
67 parse_function_list_resp3_to_resp2_legacy,
68 parse_function_list_unified,
69 parse_geopos_resp3_to_resp2_legacy,
70 parse_geopos_unified,
71 parse_geosearch_generic,
72 parse_geosearch_generic_unified,
73 parse_hscan,
74 parse_info,
75 parse_lcs_idx_resp3_to_resp2_legacy,
76 parse_lcs_idx_unified,
77 parse_list_of_dicts,
78 parse_list_of_dicts_resp3,
79 parse_memory_stats,
80 parse_memory_stats_resp3,
81 parse_memory_stats_unified,
82 parse_pubsub_numsub,
83 parse_scan,
84 parse_sentinel_get_master,
85 parse_sentinel_master,
86 parse_sentinel_master_resp3_to_resp2_legacy,
87 parse_sentinel_master_unified,
88 parse_sentinel_master_unified_resp3,
89 parse_sentinel_masters,
90 parse_sentinel_masters_resp3,
91 parse_sentinel_masters_resp3_to_resp2_legacy,
92 parse_sentinel_masters_unified,
93 parse_sentinel_masters_unified_resp3,
94 parse_sentinel_slaves_and_sentinels,
95 parse_sentinel_slaves_and_sentinels_resp3,
96 parse_sentinel_slaves_and_sentinels_resp3_to_resp2_legacy,
97 parse_sentinel_slaves_and_sentinels_unified,
98 parse_sentinel_slaves_and_sentinels_unified_resp3,
99 parse_sentinel_state_resp3,
100 parse_set_result,
101 parse_slowlog_get,
102 parse_stralgo,
103 parse_stralgo_resp3_unified,
104 parse_stralgo_unified,
105 parse_stream_list,
106 parse_xautoclaim,
107 parse_xclaim,
108 parse_xinfo_stream,
109 parse_xpending,
110 parse_xread,
111 parse_xread_resp3,
112 parse_xread_resp3_to_resp2_legacy,
113 parse_xread_unified,
114 parse_zadd,
115 parse_zmscore,
116 parse_zscan,
117 parse_zscan_unified,
118 sort_return_tuples,
119 string_keys_to_dict,
120 timestamp_to_datetime,
121 zmpop_resp3_to_resp2_legacy,
122 zmpop_unified,
123 zpop_score_pairs,
124 zpop_score_pairs_resp3_to_resp2_legacy,
125 zpop_score_pairs_resp3_unified,
126 zpop_score_pairs_unified,
127 zset_score_for_rank,
128 zset_score_for_rank_resp3,
129 zset_score_for_rank_resp3_to_resp2_legacy,
130 zset_score_for_rank_unified,
131 zset_score_pairs,
132 zset_score_pairs_resp3,
133 zset_score_pairs_resp3_to_resp2_legacy,
134 zset_score_pairs_resp3_to_resp2_legacy_flat,
135 zset_score_pairs_unified,
136)
137
138_RedisCallbacks = {
139 **string_keys_to_dict(
140 "AUTH COPY EXPIRE EXPIREAT HEXISTS HMSET MOVE MSETNX PERSIST PSETEX "
141 "PEXPIRE PEXPIREAT RENAMENX SETEX SETNX SMOVE",
142 bool,
143 ),
144 **string_keys_to_dict("HINCRBYFLOAT INCRBYFLOAT", float),
145 **string_keys_to_dict(
146 "ASKING FLUSHALL FLUSHDB LSET LTRIM MSET PFMERGE READONLY READWRITE "
147 "RENAME SAVE SELECT SHUTDOWN SLAVEOF SWAPDB WATCH UNWATCH",
148 bool_ok,
149 ),
150 **string_keys_to_dict("XREAD XREADGROUP", parse_xread),
151 **string_keys_to_dict(
152 "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH",
153 parse_geosearch_generic,
154 ),
155 **string_keys_to_dict("XRANGE XREVRANGE", parse_stream_list),
156 "ACL GETUSER": parse_acl_getuser,
157 "ACL LOAD": bool_ok,
158 "ACL LOG": parse_acl_log,
159 "ACL SETUSER": bool_ok,
160 "ACL SAVE": bool_ok,
161 "CLIENT INFO": parse_client_info,
162 "CLIENT KILL": parse_client_kill,
163 "CLIENT LIST": parse_client_list,
164 "CLIENT PAUSE": bool_ok,
165 "CLIENT SETINFO": bool_ok,
166 "CLIENT SETNAME": bool_ok,
167 "CLIENT UNBLOCK": bool,
168 "CLUSTER ADDSLOTS": bool_ok,
169 "CLUSTER ADDSLOTSRANGE": bool_ok,
170 "CLUSTER DELSLOTS": bool_ok,
171 "CLUSTER DELSLOTSRANGE": bool_ok,
172 "CLUSTER FAILOVER": bool_ok,
173 "CLUSTER FORGET": bool_ok,
174 "CLUSTER INFO": parse_cluster_info,
175 "CLUSTER MEET": bool_ok,
176 "CLUSTER NODES": parse_cluster_nodes,
177 "CLUSTER REPLICAS": parse_cluster_nodes,
178 "CLUSTER REPLICATE": bool_ok,
179 "CLUSTER RESET": bool_ok,
180 "CLUSTER SAVECONFIG": bool_ok,
181 "CLUSTER SET-CONFIG-EPOCH": bool_ok,
182 "CLUSTER SETSLOT": bool_ok,
183 "CLUSTER SLAVES": parse_cluster_nodes,
184 "COMMAND": parse_command,
185 "CONFIG RESETSTAT": bool_ok,
186 "CONFIG SET": bool_ok,
187 "FUNCTION DELETE": bool_ok,
188 "FUNCTION FLUSH": bool_ok,
189 "FUNCTION RESTORE": bool_ok,
190 "GEODIST": float_or_none,
191 HIMPORT_PREPARE: bool_ok,
192 HIMPORT_SET: bool_ok,
193 HIMPORT_DISCARD: int,
194 HIMPORT_DISCARDALL: int,
195 "HSCAN": parse_hscan,
196 "INFO": parse_info,
197 "LASTSAVE": timestamp_to_datetime,
198 "MEMORY PURGE": bool_ok,
199 "MODULE LOAD": bool,
200 "MODULE UNLOAD": bool,
201 "PING": lambda r: str_if_bytes(r) == "PONG",
202 "PUBSUB NUMSUB": parse_pubsub_numsub,
203 "PUBSUB SHARDNUMSUB": parse_pubsub_numsub,
204 "QUIT": bool_ok,
205 "SET": parse_set_result,
206 "SCAN": parse_scan,
207 "SCRIPT EXISTS": lambda r: list(map(bool, r)),
208 "SCRIPT FLUSH": bool_ok,
209 "SCRIPT KILL": bool_ok,
210 "SCRIPT LOAD": str_if_bytes,
211 "SENTINEL CKQUORUM": bool_ok,
212 "SENTINEL FAILOVER": bool_ok,
213 "SENTINEL FLUSHCONFIG": bool_ok,
214 "SENTINEL GET-MASTER-ADDR-BY-NAME": parse_sentinel_get_master,
215 "SENTINEL MONITOR": bool_ok,
216 "SENTINEL RESET": bool_ok,
217 "SENTINEL REMOVE": bool_ok,
218 "SENTINEL SET": bool_ok,
219 "SLOWLOG GET": parse_slowlog_get,
220 "SLOWLOG RESET": bool_ok,
221 "SORT": sort_return_tuples,
222 "SSCAN": parse_scan,
223 "TIME": lambda x: (int(x[0]), int(x[1])),
224 "XAUTOCLAIM": parse_xautoclaim,
225 "XCLAIM": parse_xclaim,
226 "XGROUP CREATE": bool_ok,
227 "XGROUP DESTROY": bool,
228 "XGROUP SETID": bool_ok,
229 "ARINFO": parse_arinfo,
230 "XINFO STREAM": parse_xinfo_stream,
231 "XPENDING": parse_xpending,
232 "ZSCAN": parse_zscan,
233}
234
235
236_RedisCallbacksRESP2 = {
237 **string_keys_to_dict(
238 "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
239 ),
240 **string_keys_to_dict(
241 "ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
242 zset_score_pairs,
243 ),
244 **string_keys_to_dict("ZPOPMAX ZPOPMIN", zpop_score_pairs),
245 **string_keys_to_dict(
246 "ZREVRANK ZRANK",
247 zset_score_for_rank,
248 ),
249 **string_keys_to_dict("ZINCRBY ZSCORE", float_or_none),
250 **string_keys_to_dict("BGREWRITEAOF BGSAVE", lambda r: True),
251 **string_keys_to_dict("BLPOP BRPOP", lambda r: r and tuple(r) or None),
252 **string_keys_to_dict(
253 "BZPOPMAX BZPOPMIN", lambda r: r and (r[0], r[1], float(r[2])) or None
254 ),
255 "ACL CAT": lambda r: list(map(str_if_bytes, r)),
256 "ACL GENPASS": str_if_bytes,
257 "ACL HELP": lambda r: list(map(str_if_bytes, r)),
258 "ACL LIST": lambda r: list(map(str_if_bytes, r)),
259 "ACL USERS": lambda r: list(map(str_if_bytes, r)),
260 "ACL WHOAMI": str_if_bytes,
261 "CLIENT GETNAME": str_if_bytes,
262 "CLIENT TRACKINGINFO": lambda r: list(map(str_if_bytes, r)),
263 "CLUSTER GETKEYSINSLOT": lambda r: list(map(str_if_bytes, r)),
264 "COMMAND GETKEYS": lambda r: list(map(str_if_bytes, r)),
265 "CONFIG GET": parse_config_get,
266 "DEBUG OBJECT": parse_debug_object,
267 "GEOHASH": lambda r: list(map(str_if_bytes, r)),
268 "GEOPOS": lambda r: list(
269 map(lambda ll: (float(ll[0]), float(ll[1])) if ll is not None else None, r)
270 ),
271 "HGETALL": lambda r: r and pairs_to_dict(r) or {},
272 "HOTKEYS GET": lambda r: [pairs_to_dict(m) for m in r],
273 "MEMORY STATS": parse_memory_stats,
274 "MODULE LIST": lambda r: [pairs_to_dict(m) for m in r],
275 "RESET": str_if_bytes,
276 "SENTINEL MASTER": parse_sentinel_master,
277 "SENTINEL MASTERS": parse_sentinel_masters,
278 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels,
279 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels,
280 "STRALGO": parse_stralgo,
281 "XINFO CONSUMERS": parse_list_of_dicts,
282 "XINFO GROUPS": parse_list_of_dicts,
283 "ZADD": parse_zadd,
284 "ZMSCORE": parse_zmscore,
285}
286
287
288_RedisCallbacksRESP3 = {
289 **string_keys_to_dict(
290 "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
291 ),
292 **string_keys_to_dict(
293 "ZRANGE ZINTER ZPOPMAX ZPOPMIN HGETALL XREADGROUP",
294 lambda r, **kwargs: r,
295 ),
296 **string_keys_to_dict(
297 "ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
298 zset_score_pairs_resp3,
299 ),
300 **string_keys_to_dict(
301 "ZREVRANK ZRANK",
302 zset_score_for_rank_resp3,
303 ),
304 **string_keys_to_dict("XREAD XREADGROUP", parse_xread_resp3),
305 "ACL LOG": lambda r: (
306 [
307 {str_if_bytes(key): str_if_bytes(value) for key, value in x.items()}
308 for x in r
309 ]
310 if isinstance(r, list)
311 else bool_ok(r)
312 ),
313 "COMMAND": parse_command_resp3,
314 "CONFIG GET": parse_config_get_resp3_to_resp2_legacy,
315 "MEMORY STATS": parse_memory_stats_resp3,
316 "SENTINEL MASTER": parse_sentinel_state_resp3,
317 "SENTINEL MASTERS": parse_sentinel_masters_resp3,
318 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_resp3,
319 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_resp3,
320 "STRALGO": lambda r, **options: (
321 {str_if_bytes(key): str_if_bytes(value) for key, value in r.items()}
322 if isinstance(r, dict)
323 else str_if_bytes(r)
324 ),
325 "XINFO CONSUMERS": parse_list_of_dicts_resp3,
326 "XINFO GROUPS": parse_list_of_dicts_resp3,
327}
328
329
330# RESP2 wire, unified response shapes (``legacy_responses=False``).
331_RedisCallbacksRESP2Unified: dict[str, Callable[..., Any]] = {
332 **_RedisCallbacksRESP2,
333 **string_keys_to_dict(
334 "ZDIFF ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
335 zset_score_pairs_unified,
336 ),
337 **string_keys_to_dict(
338 "ZPOPMAX ZPOPMIN",
339 zpop_score_pairs_unified,
340 ),
341 **string_keys_to_dict(
342 "ZREVRANK ZRANK",
343 zset_score_for_rank_unified,
344 ),
345 **string_keys_to_dict(
346 "BZPOPMAX BZPOPMIN",
347 bzpop_score_unified,
348 ),
349 **string_keys_to_dict("XREAD XREADGROUP", parse_xread_unified),
350 **string_keys_to_dict(
351 "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH",
352 parse_geosearch_generic_unified,
353 ),
354 **string_keys_to_dict("BLPOP BRPOP", lambda r: r or None),
355 **string_keys_to_dict("ZMPOP BZMPOP", zmpop_unified),
356 "ZSCAN": parse_zscan_unified,
357 "ZRANDMEMBER": zset_score_pairs_unified,
358 "HRANDFIELD": hrandfield_unified,
359 "ACL GETUSER": parse_acl_getuser_unified,
360 "CLIENT TRACKINGINFO": parse_client_trackinginfo_unified,
361 "CLUSTER GETKEYSINSLOT": lambda r, **kwargs: r,
362 "CLUSTER LINKS": parse_cluster_links_unified,
363 "COMMAND": parse_command_unified,
364 "COMMAND GETKEYS": lambda r, **kwargs: r,
365 "FUNCTION LIST": parse_function_list_unified,
366 "GEOPOS": parse_geopos_unified,
367 "LCS": parse_lcs_idx_unified,
368 "MEMORY STATS": parse_memory_stats_unified,
369 "SENTINEL MASTER": parse_sentinel_master_unified,
370 "SENTINEL MASTERS": parse_sentinel_masters_unified,
371 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_unified,
372 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_unified,
373 "STRALGO": parse_stralgo_unified,
374}
375
376
377# RESP3 wire, unified response shapes (``legacy_responses=False``).
378_RedisCallbacksRESP3Unified: dict[str, Callable[..., Any]] = {
379 **_RedisCallbacksRESP3,
380 **string_keys_to_dict(
381 "ZDIFF ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
382 zset_score_pairs_resp3,
383 ),
384 **string_keys_to_dict(
385 "ZPOPMAX ZPOPMIN",
386 zpop_score_pairs_resp3_unified,
387 ),
388 **string_keys_to_dict(
389 "BZPOPMAX BZPOPMIN",
390 bzpop_score_unified,
391 ),
392 **string_keys_to_dict("XREAD XREADGROUP", parse_xread_unified),
393 **string_keys_to_dict(
394 "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH",
395 parse_geosearch_generic_unified,
396 ),
397 "ZSCAN": parse_zscan_unified,
398 "ZRANDMEMBER": zset_score_pairs_resp3,
399 "ACL CAT": lambda r: list(map(str_if_bytes, r)),
400 "ACL GENPASS": str_if_bytes,
401 "ACL HELP": lambda r: list(map(str_if_bytes, r)),
402 "ACL LIST": lambda r: list(map(str_if_bytes, r)),
403 "ACL LOG": parse_acl_log_resp3_unified,
404 "ACL USERS": lambda r: list(map(str_if_bytes, r)),
405 "ACL WHOAMI": str_if_bytes,
406 "CLIENT GETNAME": str_if_bytes,
407 "CLIENT TRACKINGINFO": parse_client_trackinginfo_unified,
408 "CLUSTER LINKS": parse_cluster_links_unified,
409 "COMMAND": parse_command_unified,
410 "FUNCTION LIST": parse_function_list_unified,
411 "GEOHASH": lambda r: list(map(str_if_bytes, r)),
412 "LCS": parse_lcs_idx_unified,
413 "RESET": str_if_bytes,
414 "SENTINEL MASTER": parse_sentinel_master_unified_resp3,
415 "SENTINEL MASTERS": parse_sentinel_masters_unified_resp3,
416 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_unified_resp3,
417 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_unified_resp3,
418 "STRALGO": parse_stralgo_resp3_unified,
419}
420
421
422# RESP3 wire converted back to the legacy RESP2 Python shapes. Only the
423# entries needed to undo RESP3-side differences are listed; everything
424# else falls through to ``_RedisCallbacks``. Scores are re-encoded to bytes
425# before being passed to ``score_cast_func`` so the callable observes the
426# same input type it would on a RESP2 connection.
427_RedisCallbacksRESP3toRESP2Legacy: dict[str, Callable[..., Any]] = {
428 **string_keys_to_dict(
429 "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
430 ),
431 **string_keys_to_dict(
432 "ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
433 zset_score_pairs_resp3_to_resp2_legacy,
434 ),
435 "ZDIFF": zset_score_pairs_resp3_to_resp2_legacy_flat,
436 "ZRANDMEMBER": zset_score_pairs_resp3_to_resp2_legacy_flat,
437 **string_keys_to_dict(
438 "ZPOPMAX ZPOPMIN",
439 zpop_score_pairs_resp3_to_resp2_legacy,
440 ),
441 **string_keys_to_dict(
442 "BZPOPMAX BZPOPMIN",
443 bzpop_score_resp3_to_resp2_legacy,
444 ),
445 **string_keys_to_dict(
446 "ZREVRANK ZRANK",
447 zset_score_for_rank_resp3_to_resp2_legacy,
448 ),
449 **string_keys_to_dict("BGREWRITEAOF BGSAVE", lambda r: True),
450 **string_keys_to_dict("XREAD XREADGROUP", parse_xread_resp3_to_resp2_legacy),
451 **string_keys_to_dict("BLPOP BRPOP", lambda r: r and tuple(r) or None),
452 **string_keys_to_dict("ZMPOP BZMPOP", zmpop_resp3_to_resp2_legacy),
453 "HRANDFIELD": hrandfield_resp3_to_resp2_legacy,
454 "ACL CAT": lambda r: list(map(str_if_bytes, r)),
455 "ACL GENPASS": str_if_bytes,
456 "ACL GETUSER": parse_acl_getuser_resp3_to_resp2_legacy,
457 "ACL HELP": lambda r: list(map(str_if_bytes, r)),
458 "ACL LIST": lambda r: list(map(str_if_bytes, r)),
459 "ACL LOG": parse_acl_log_resp3_to_resp2_legacy,
460 "ACL USERS": lambda r: list(map(str_if_bytes, r)),
461 "ACL WHOAMI": str_if_bytes,
462 "CLIENT GETNAME": str_if_bytes,
463 "CLIENT TRACKINGINFO": parse_client_trackinginfo_resp3_to_resp2_legacy,
464 "CLUSTER GETKEYSINSLOT": lambda r: list(map(str_if_bytes, r)),
465 "CLUSTER LINKS": parse_cluster_links_resp3_to_resp2_legacy,
466 "COMMAND GETKEYS": lambda r: list(map(str_if_bytes, r)),
467 "CONFIG GET": parse_config_get_resp3_to_resp2_legacy,
468 "DEBUG OBJECT": parse_debug_object,
469 "FUNCTION LIST": parse_function_list_resp3_to_resp2_legacy,
470 "GEOHASH": lambda r: list(map(str_if_bytes, r)),
471 "GEOPOS": parse_geopos_resp3_to_resp2_legacy,
472 "LCS": parse_lcs_idx_resp3_to_resp2_legacy,
473 "MEMORY STATS": parse_memory_stats_resp3,
474 "RESET": str_if_bytes,
475 "SENTINEL MASTER": parse_sentinel_master_resp3_to_resp2_legacy,
476 "SENTINEL MASTERS": parse_sentinel_masters_resp3_to_resp2_legacy,
477 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_resp3_to_resp2_legacy,
478 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_resp3_to_resp2_legacy,
479 "XINFO CONSUMERS": parse_list_of_dicts_resp3,
480 "XINFO GROUPS": parse_list_of_dicts_resp3,
481}
482
483
484def get_response_callbacks(
485 user_protocol: Optional[int],
486 legacy_responses: bool,
487) -> dict[str, Callable[..., Any]]:
488 """Return the merged callback dict for the given (protocol, legacy)
489 combination.
490
491 ``user_protocol`` is the value the user supplied to the client
492 constructor (``None`` means "not specified"). ``legacy_responses``
493 defaults to ``True`` and selects today's RESP2-style Python shapes
494 even when the wire protocol is RESP3.
495
496 Callers wrap the returned dict in ``CaseInsensitiveDict``.
497 """
498 callbacks: dict[str, Callable[..., Any]] = dict(_RedisCallbacks)
499 if legacy_responses:
500 if user_protocol is None:
501 callbacks.update(_RedisCallbacksRESP3toRESP2Legacy)
502 elif user_protocol in (3, "3"):
503 callbacks.update(_RedisCallbacksRESP3)
504 else:
505 callbacks.update(_RedisCallbacksRESP2)
506 else:
507 if user_protocol is None or user_protocol in (3, "3"):
508 callbacks.update(_RedisCallbacksRESP3Unified)
509 else:
510 callbacks.update(_RedisCallbacksRESP2Unified)
511 return callbacks