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 "BLESS SCAN": parse_scan,
162 "CLIENT INFO": parse_client_info,
163 "CLIENT KILL": parse_client_kill,
164 "CLIENT LIST": parse_client_list,
165 "CLIENT PAUSE": bool_ok,
166 "CLIENT SETINFO": bool_ok,
167 "CLIENT SETNAME": bool_ok,
168 "CLIENT UNBLOCK": bool,
169 "CLUSTER ADDSLOTS": bool_ok,
170 "CLUSTER ADDSLOTSRANGE": bool_ok,
171 "CLUSTER DELSLOTS": bool_ok,
172 "CLUSTER DELSLOTSRANGE": bool_ok,
173 "CLUSTER FAILOVER": bool_ok,
174 "CLUSTER FORGET": bool_ok,
175 "CLUSTER INFO": parse_cluster_info,
176 "CLUSTER MEET": bool_ok,
177 "CLUSTER NODES": parse_cluster_nodes,
178 "CLUSTER REPLICAS": parse_cluster_nodes,
179 "CLUSTER REPLICATE": bool_ok,
180 "CLUSTER RESET": bool_ok,
181 "CLUSTER SAVECONFIG": bool_ok,
182 "CLUSTER SET-CONFIG-EPOCH": bool_ok,
183 "CLUSTER SETSLOT": bool_ok,
184 "CLUSTER SLAVES": parse_cluster_nodes,
185 "COMMAND": parse_command,
186 "CONFIG RESETSTAT": bool_ok,
187 "CONFIG SET": bool_ok,
188 "FUNCTION DELETE": bool_ok,
189 "FUNCTION FLUSH": bool_ok,
190 "FUNCTION RESTORE": bool_ok,
191 "GEODIST": float_or_none,
192 HIMPORT_PREPARE: bool_ok,
193 HIMPORT_SET: bool_ok,
194 HIMPORT_DISCARD: int,
195 HIMPORT_DISCARDALL: int,
196 "HSCAN": parse_hscan,
197 "INFO": parse_info,
198 "LASTSAVE": timestamp_to_datetime,
199 "MEMORY PURGE": bool_ok,
200 "MODULE LOAD": bool,
201 "MODULE UNLOAD": bool,
202 "PING": lambda r: str_if_bytes(r) == "PONG",
203 "PUBSUB NUMSUB": parse_pubsub_numsub,
204 "PUBSUB SHARDNUMSUB": parse_pubsub_numsub,
205 "QUIT": bool_ok,
206 "SET": parse_set_result,
207 "SCAN": parse_scan,
208 "SCRIPT EXISTS": lambda r: list(map(bool, r)),
209 "SCRIPT FLUSH": bool_ok,
210 "SCRIPT KILL": bool_ok,
211 "SCRIPT LOAD": str_if_bytes,
212 "SENTINEL CKQUORUM": bool_ok,
213 "SENTINEL FAILOVER": bool_ok,
214 "SENTINEL FLUSHCONFIG": bool_ok,
215 "SENTINEL GET-MASTER-ADDR-BY-NAME": parse_sentinel_get_master,
216 "SENTINEL MONITOR": bool_ok,
217 "SENTINEL RESET": bool_ok,
218 "SENTINEL REMOVE": bool_ok,
219 "SENTINEL SET": bool_ok,
220 "SLOWLOG GET": parse_slowlog_get,
221 "SLOWLOG RESET": bool_ok,
222 "SORT": sort_return_tuples,
223 "SSCAN": parse_scan,
224 "TIME": lambda x: (int(x[0]), int(x[1])),
225 "XAUTOCLAIM": parse_xautoclaim,
226 "XCLAIM": parse_xclaim,
227 "XGROUP CREATE": bool_ok,
228 "XGROUP DESTROY": bool,
229 "XGROUP SETID": bool_ok,
230 "ARINFO": parse_arinfo,
231 "XINFO STREAM": parse_xinfo_stream,
232 "XPENDING": parse_xpending,
233 "ZSCAN": parse_zscan,
234}
235
236
237_RedisCallbacksRESP2 = {
238 **string_keys_to_dict(
239 "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
240 ),
241 **string_keys_to_dict(
242 "ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
243 zset_score_pairs,
244 ),
245 **string_keys_to_dict("ZPOPMAX ZPOPMIN", zpop_score_pairs),
246 **string_keys_to_dict(
247 "ZREVRANK ZRANK",
248 zset_score_for_rank,
249 ),
250 **string_keys_to_dict("ZINCRBY ZSCORE", float_or_none),
251 **string_keys_to_dict("BGREWRITEAOF BGSAVE", lambda r: True),
252 **string_keys_to_dict("BLPOP BRPOP", lambda r: r and tuple(r) or None),
253 **string_keys_to_dict(
254 "BZPOPMAX BZPOPMIN", lambda r: r and (r[0], r[1], float(r[2])) or None
255 ),
256 "ACL CAT": lambda r: list(map(str_if_bytes, r)),
257 "ACL GENPASS": str_if_bytes,
258 "ACL HELP": lambda r: list(map(str_if_bytes, r)),
259 "ACL LIST": lambda r: list(map(str_if_bytes, r)),
260 "ACL USERS": lambda r: list(map(str_if_bytes, r)),
261 "ACL WHOAMI": str_if_bytes,
262 "CLIENT GETNAME": str_if_bytes,
263 "CLIENT TRACKINGINFO": lambda r: list(map(str_if_bytes, r)),
264 "CLUSTER GETKEYSINSLOT": lambda r: list(map(str_if_bytes, r)),
265 "COMMAND GETKEYS": lambda r: list(map(str_if_bytes, r)),
266 "CONFIG GET": parse_config_get,
267 "DEBUG OBJECT": parse_debug_object,
268 "GEOHASH": lambda r: list(map(str_if_bytes, r)),
269 "GEOPOS": lambda r: list(
270 map(lambda ll: (float(ll[0]), float(ll[1])) if ll is not None else None, r)
271 ),
272 "HGETALL": lambda r: r and pairs_to_dict(r) or {},
273 "HOTKEYS GET": lambda r: [pairs_to_dict(m) for m in r],
274 "MEMORY STATS": parse_memory_stats,
275 "MODULE LIST": lambda r: [pairs_to_dict(m) for m in r],
276 "RESET": str_if_bytes,
277 "SENTINEL MASTER": parse_sentinel_master,
278 "SENTINEL MASTERS": parse_sentinel_masters,
279 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels,
280 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels,
281 "STRALGO": parse_stralgo,
282 "XINFO CONSUMERS": parse_list_of_dicts,
283 "XINFO GROUPS": parse_list_of_dicts,
284 "ZADD": parse_zadd,
285 "ZMSCORE": parse_zmscore,
286}
287
288
289_RedisCallbacksRESP3 = {
290 **string_keys_to_dict(
291 "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
292 ),
293 **string_keys_to_dict(
294 "ZRANGE ZINTER ZPOPMAX ZPOPMIN HGETALL XREADGROUP",
295 lambda r, **kwargs: r,
296 ),
297 **string_keys_to_dict(
298 "ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
299 zset_score_pairs_resp3,
300 ),
301 **string_keys_to_dict(
302 "ZREVRANK ZRANK",
303 zset_score_for_rank_resp3,
304 ),
305 **string_keys_to_dict("XREAD XREADGROUP", parse_xread_resp3),
306 "ACL LOG": lambda r: (
307 [
308 {str_if_bytes(key): str_if_bytes(value) for key, value in x.items()}
309 for x in r
310 ]
311 if isinstance(r, list)
312 else bool_ok(r)
313 ),
314 "COMMAND": parse_command_resp3,
315 "CONFIG GET": parse_config_get_resp3_to_resp2_legacy,
316 "MEMORY STATS": parse_memory_stats_resp3,
317 "SENTINEL MASTER": parse_sentinel_state_resp3,
318 "SENTINEL MASTERS": parse_sentinel_masters_resp3,
319 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_resp3,
320 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_resp3,
321 "STRALGO": lambda r, **options: (
322 {str_if_bytes(key): str_if_bytes(value) for key, value in r.items()}
323 if isinstance(r, dict)
324 else str_if_bytes(r)
325 ),
326 "XINFO CONSUMERS": parse_list_of_dicts_resp3,
327 "XINFO GROUPS": parse_list_of_dicts_resp3,
328}
329
330
331# RESP2 wire, unified response shapes (``legacy_responses=False``).
332_RedisCallbacksRESP2Unified: dict[str, Callable[..., Any]] = {
333 **_RedisCallbacksRESP2,
334 **string_keys_to_dict(
335 "ZDIFF ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
336 zset_score_pairs_unified,
337 ),
338 **string_keys_to_dict(
339 "ZPOPMAX ZPOPMIN",
340 zpop_score_pairs_unified,
341 ),
342 **string_keys_to_dict(
343 "ZREVRANK ZRANK",
344 zset_score_for_rank_unified,
345 ),
346 **string_keys_to_dict(
347 "BZPOPMAX BZPOPMIN",
348 bzpop_score_unified,
349 ),
350 **string_keys_to_dict("XREAD XREADGROUP", parse_xread_unified),
351 **string_keys_to_dict(
352 "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH",
353 parse_geosearch_generic_unified,
354 ),
355 **string_keys_to_dict("BLPOP BRPOP", lambda r: r or None),
356 **string_keys_to_dict("ZMPOP BZMPOP", zmpop_unified),
357 "ZSCAN": parse_zscan_unified,
358 "ZRANDMEMBER": zset_score_pairs_unified,
359 "HRANDFIELD": hrandfield_unified,
360 "ACL GETUSER": parse_acl_getuser_unified,
361 "CLIENT TRACKINGINFO": parse_client_trackinginfo_unified,
362 "CLUSTER GETKEYSINSLOT": lambda r, **kwargs: r,
363 "CLUSTER LINKS": parse_cluster_links_unified,
364 "COMMAND": parse_command_unified,
365 "COMMAND GETKEYS": lambda r, **kwargs: r,
366 "FUNCTION LIST": parse_function_list_unified,
367 "GEOPOS": parse_geopos_unified,
368 "LCS": parse_lcs_idx_unified,
369 "MEMORY STATS": parse_memory_stats_unified,
370 "SENTINEL MASTER": parse_sentinel_master_unified,
371 "SENTINEL MASTERS": parse_sentinel_masters_unified,
372 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_unified,
373 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_unified,
374 "STRALGO": parse_stralgo_unified,
375}
376
377
378# RESP3 wire, unified response shapes (``legacy_responses=False``).
379_RedisCallbacksRESP3Unified: dict[str, Callable[..., Any]] = {
380 **_RedisCallbacksRESP3,
381 **string_keys_to_dict(
382 "ZDIFF ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
383 zset_score_pairs_resp3,
384 ),
385 **string_keys_to_dict(
386 "ZPOPMAX ZPOPMIN",
387 zpop_score_pairs_resp3_unified,
388 ),
389 **string_keys_to_dict(
390 "BZPOPMAX BZPOPMIN",
391 bzpop_score_unified,
392 ),
393 **string_keys_to_dict("XREAD XREADGROUP", parse_xread_unified),
394 **string_keys_to_dict(
395 "GEORADIUS GEORADIUSBYMEMBER GEOSEARCH",
396 parse_geosearch_generic_unified,
397 ),
398 "ZSCAN": parse_zscan_unified,
399 "ZRANDMEMBER": zset_score_pairs_resp3,
400 "ACL CAT": lambda r: list(map(str_if_bytes, r)),
401 "ACL GENPASS": str_if_bytes,
402 "ACL HELP": lambda r: list(map(str_if_bytes, r)),
403 "ACL LIST": lambda r: list(map(str_if_bytes, r)),
404 "ACL LOG": parse_acl_log_resp3_unified,
405 "ACL USERS": lambda r: list(map(str_if_bytes, r)),
406 "ACL WHOAMI": str_if_bytes,
407 "CLIENT GETNAME": str_if_bytes,
408 "CLIENT TRACKINGINFO": parse_client_trackinginfo_unified,
409 "CLUSTER LINKS": parse_cluster_links_unified,
410 "COMMAND": parse_command_unified,
411 "FUNCTION LIST": parse_function_list_unified,
412 "GEOHASH": lambda r: list(map(str_if_bytes, r)),
413 "LCS": parse_lcs_idx_unified,
414 "RESET": str_if_bytes,
415 "SENTINEL MASTER": parse_sentinel_master_unified_resp3,
416 "SENTINEL MASTERS": parse_sentinel_masters_unified_resp3,
417 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_unified_resp3,
418 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_unified_resp3,
419 "STRALGO": parse_stralgo_resp3_unified,
420}
421
422
423# RESP3 wire converted back to the legacy RESP2 Python shapes. Only the
424# entries needed to undo RESP3-side differences are listed; everything
425# else falls through to ``_RedisCallbacks``. Scores are re-encoded to bytes
426# before being passed to ``score_cast_func`` so the callable observes the
427# same input type it would on a RESP2 connection.
428_RedisCallbacksRESP3toRESP2Legacy: dict[str, Callable[..., Any]] = {
429 **string_keys_to_dict(
430 "SDIFF SINTER SMEMBERS SUNION", lambda r: r and set(r) or set()
431 ),
432 **string_keys_to_dict(
433 "ZINTER ZRANGE ZRANGEBYSCORE ZREVRANGE ZREVRANGEBYSCORE ZUNION",
434 zset_score_pairs_resp3_to_resp2_legacy,
435 ),
436 "ZDIFF": zset_score_pairs_resp3_to_resp2_legacy_flat,
437 "ZRANDMEMBER": zset_score_pairs_resp3_to_resp2_legacy_flat,
438 **string_keys_to_dict(
439 "ZPOPMAX ZPOPMIN",
440 zpop_score_pairs_resp3_to_resp2_legacy,
441 ),
442 **string_keys_to_dict(
443 "BZPOPMAX BZPOPMIN",
444 bzpop_score_resp3_to_resp2_legacy,
445 ),
446 **string_keys_to_dict(
447 "ZREVRANK ZRANK",
448 zset_score_for_rank_resp3_to_resp2_legacy,
449 ),
450 **string_keys_to_dict("BGREWRITEAOF BGSAVE", lambda r: True),
451 **string_keys_to_dict("XREAD XREADGROUP", parse_xread_resp3_to_resp2_legacy),
452 **string_keys_to_dict("BLPOP BRPOP", lambda r: r and tuple(r) or None),
453 **string_keys_to_dict("ZMPOP BZMPOP", zmpop_resp3_to_resp2_legacy),
454 "HRANDFIELD": hrandfield_resp3_to_resp2_legacy,
455 "ACL CAT": lambda r: list(map(str_if_bytes, r)),
456 "ACL GENPASS": str_if_bytes,
457 "ACL GETUSER": parse_acl_getuser_resp3_to_resp2_legacy,
458 "ACL HELP": lambda r: list(map(str_if_bytes, r)),
459 "ACL LIST": lambda r: list(map(str_if_bytes, r)),
460 "ACL LOG": parse_acl_log_resp3_to_resp2_legacy,
461 "ACL USERS": lambda r: list(map(str_if_bytes, r)),
462 "ACL WHOAMI": str_if_bytes,
463 "CLIENT GETNAME": str_if_bytes,
464 "CLIENT TRACKINGINFO": parse_client_trackinginfo_resp3_to_resp2_legacy,
465 "CLUSTER GETKEYSINSLOT": lambda r: list(map(str_if_bytes, r)),
466 "CLUSTER LINKS": parse_cluster_links_resp3_to_resp2_legacy,
467 "COMMAND GETKEYS": lambda r: list(map(str_if_bytes, r)),
468 "CONFIG GET": parse_config_get_resp3_to_resp2_legacy,
469 "DEBUG OBJECT": parse_debug_object,
470 "FUNCTION LIST": parse_function_list_resp3_to_resp2_legacy,
471 "GEOHASH": lambda r: list(map(str_if_bytes, r)),
472 "GEOPOS": parse_geopos_resp3_to_resp2_legacy,
473 "LCS": parse_lcs_idx_resp3_to_resp2_legacy,
474 "MEMORY STATS": parse_memory_stats_resp3,
475 "RESET": str_if_bytes,
476 "SENTINEL MASTER": parse_sentinel_master_resp3_to_resp2_legacy,
477 "SENTINEL MASTERS": parse_sentinel_masters_resp3_to_resp2_legacy,
478 "SENTINEL SENTINELS": parse_sentinel_slaves_and_sentinels_resp3_to_resp2_legacy,
479 "SENTINEL SLAVES": parse_sentinel_slaves_and_sentinels_resp3_to_resp2_legacy,
480 "XINFO CONSUMERS": parse_list_of_dicts_resp3,
481 "XINFO GROUPS": parse_list_of_dicts_resp3,
482}
483
484
485def get_response_callbacks(
486 user_protocol: Optional[int],
487 legacy_responses: bool,
488) -> dict[str, Callable[..., Any]]:
489 """Return the merged callback dict for the given (protocol, legacy)
490 combination.
491
492 ``user_protocol`` is the value the user supplied to the client
493 constructor (``None`` means "not specified"). ``legacy_responses``
494 defaults to ``True`` and selects today's RESP2-style Python shapes
495 even when the wire protocol is RESP3.
496
497 Callers wrap the returned dict in ``CaseInsensitiveDict``.
498 """
499 callbacks: dict[str, Callable[..., Any]] = dict(_RedisCallbacks)
500 if legacy_responses:
501 if user_protocol is None:
502 callbacks.update(_RedisCallbacksRESP3toRESP2Legacy)
503 elif user_protocol in (3, "3"):
504 callbacks.update(_RedisCallbacksRESP3)
505 else:
506 callbacks.update(_RedisCallbacksRESP2)
507 else:
508 if user_protocol is None or user_protocol in (3, "3"):
509 callbacks.update(_RedisCallbacksRESP3Unified)
510 else:
511 callbacks.update(_RedisCallbacksRESP2Unified)
512 return callbacks