1- from typing import Optional
1+ from typing import (
2+ Callable ,
3+ ContextManager ,
4+ Iterable ,
5+ List ,
6+ Literal ,
7+ Mapping ,
8+ Optional ,
9+ Protocol ,
10+ TypeVar ,
11+ overload ,
12+ )
213from urllib .parse import quote , urlparse , urlunparse
314import logging
415import time
516
17+ from redis .typing import EncodableT , ExpiryT , FieldT , KeysT , KeyT , ZScoreBoundT
18+ import redis .client
19+
620import redis
721
822from .base_config import BaseConfig
@@ -15,7 +29,158 @@ class RedisConfig(BaseConfig):
1529 redis_password : Optional [str ] = None
1630
1731
18- def get_redis_client (config : RedisConfig ) -> "redis.Redis[bytes]" :
32+ ScoreT = TypeVar ("ScoreT" )
33+
34+
35+ class TypedRedis (Protocol ):
36+ """
37+ A typed subset of redis.Redis methods used by flatpak-indexer.
38+
39+ The redis-py type annotations are a mess: async and sync return types
40+ are mixed together, and there is no representation of the fact that
41+ the client *might* decode the responses from bytes to str, or not,
42+ depending on how it was constructed.
43+
44+ This Protocol represents the methods we actually use, with more precise
45+ types - we simply cast the redis.Redis instances we get to this type.
46+ """
47+
48+ def delete (self , * names : KeyT ) -> int : ...
49+
50+ def execute (self ) -> List [object ]: ...
51+
52+ def exists (self , name : KeyT ) -> bool : ...
53+
54+ def hmget (self , name : KeyT , keys : KeysT , * args : KeyT ) -> list [bytes | None ]: ...
55+
56+ def hget (self , name : KeyT , key : KeyT ) -> bytes | None : ...
57+
58+ def get (self , name : KeyT ) -> Optional [bytes ]: ...
59+
60+ @overload
61+ def hset (self , name : KeyT , key : KeyT , value : EncodableT ) -> int : ...
62+
63+ @overload
64+ def hset (self , name : KeyT , mapping : Mapping [str , EncodableT ]) -> int : ...
65+
66+ def mget (self , keys : KeysT , * args : KeyT ) -> list [bytes | None ]: ...
67+
68+ def multi (self ): ...
69+
70+ def pipeline (self , * args , ** kwargs ) -> "TypedPipeline" : ...
71+
72+ def publish (self , channel : KeyT , message : EncodableT ) -> int : ...
73+
74+ def sadd (self , name : KeyT , * values : FieldT ) -> int : ...
75+
76+ def scan_iter (self , match : Optional [EncodableT ] = None ) -> Iterable [str ]: ...
77+
78+ def scard (self , name : KeyT ) -> int : ...
79+
80+ def set (self , name : KeyT , value : EncodableT ) -> bool : ...
81+
82+ def setex (self , name : KeyT , time : ExpiryT , value : EncodableT ) -> bool : ...
83+
84+ @overload
85+ def srandmember (self , name : KeyT , number : None = None ) -> Optional [bytes ]: ...
86+
87+ @overload
88+ def srandmember (self , name : KeyT , number : int ) -> List [bytes ]: ...
89+
90+ def srem (self , name : KeyT , * values : FieldT ) -> int : ...
91+
92+ def transaction (self , func : Callable [["TypedPipeline" ], None ]): ...
93+
94+ def watch (self , keys : KeysT ): ...
95+
96+ def zadd (self , name : KeyT , mapping : dict [EncodableT , float ], xx : bool = False ) -> int : ...
97+
98+ def zcard (self , name : KeyT ) -> int : ...
99+
100+ @overload
101+ def zrange (
102+ self ,
103+ name : KeyT ,
104+ start : int ,
105+ end : int ,
106+ * ,
107+ desc : bool = False ,
108+ withscores : Literal [False ] = False ,
109+ score_cast_func : type | Callable = float ,
110+ byscore : bool = False ,
111+ bylex : bool = False ,
112+ offset : Optional [int ] = None ,
113+ num : Optional [int ] = None ,
114+ ) -> list [bytes ]: ...
115+
116+ @overload
117+ def zrange (
118+ self ,
119+ name : KeyT ,
120+ start : int ,
121+ end : int ,
122+ * ,
123+ desc : bool = False ,
124+ withscores : Literal [True ],
125+ score_cast_func : type | Callable [[float ], ScoreT ] = float ,
126+ byscore : bool = False ,
127+ bylex : bool = False ,
128+ offset : Optional [int ] = None ,
129+ num : Optional [int ] = None ,
130+ ) -> list [tuple [bytes , ScoreT ]]: ...
131+
132+ def zrangebylex (
133+ self ,
134+ name : KeyT ,
135+ min : EncodableT ,
136+ max : EncodableT ,
137+ start : Optional [int ] = None ,
138+ num : Optional [int ] = None ,
139+ ) -> list [bytes ]: ...
140+
141+ @overload
142+ def zrangebyscore (
143+ self ,
144+ name : KeyT ,
145+ min : ZScoreBoundT ,
146+ max : ZScoreBoundT ,
147+ * ,
148+ start : Optional [int ] = None ,
149+ num : Optional [int ] = None ,
150+ withscores : Literal [False ] = False ,
151+ score_cast_func : type | Callable = float ,
152+ ) -> list [bytes ]: ...
153+
154+ @overload
155+ def zrangebyscore (
156+ self ,
157+ name : KeyT ,
158+ min : ZScoreBoundT ,
159+ max : ZScoreBoundT ,
160+ * ,
161+ start : Optional [int ] = None ,
162+ num : Optional [int ] = None ,
163+ withscores : Literal [True ],
164+ score_cast_func : type | Callable [[float ], ScoreT ] = float ,
165+ ) -> list [tuple [bytes , ScoreT ]]: ...
166+
167+ def zrem (self , name : KeyT , * values : EncodableT ) -> int : ...
168+
169+ def zremrangebyscore (
170+ self ,
171+ name : KeyT ,
172+ min : ZScoreBoundT ,
173+ max : ZScoreBoundT ,
174+ ) -> int : ...
175+
176+ def zscore (self , name : KeyT , value : EncodableT ) -> float | None : ...
177+
178+
179+ class TypedPipeline (TypedRedis , ContextManager ["TypedPipeline" ]):
180+ pass
181+
182+
183+ def get_redis_client (config : RedisConfig ) -> TypedRedis :
19184 url = config .redis_url
20185
21186 # redis.Redis.from_url() doesn't support passing the password separately
0 commit comments