11import math
2- from collections .abc import AsyncIterator , Awaitable , Callable
2+ from collections .abc import AsyncIterator , Awaitable , Callable , Coroutine
33from typing import TYPE_CHECKING , Any , Optional , TypeAlias
44
55from redis .exceptions import ResponseError
@@ -47,6 +47,8 @@ def __init__(
4747 assert config .stream_sub
4848 self ._stream_sub = config .stream_sub
4949 self .last_id = config .stream_sub .last_id
50+ self .min_idle_time = config .stream_sub .min_idle_time
51+ self .autoclaim_start_id = "0-0"
5052
5153 @property
5254 def stream_sub (self ) -> "StreamSub" :
@@ -139,7 +141,7 @@ def read(
139141 noack = stream .no_ack ,
140142 )
141143
142- else :
144+ elif self . stream_sub . min_idle_time is None :
143145
144146 def read (
145147 last_id : str ,
@@ -164,6 +166,50 @@ def read(
164166 count = stream .max_records ,
165167 )
166168
169+ else :
170+
171+ def read (
172+ _ : str ,
173+ ) -> Coroutine [
174+ Any ,
175+ Any ,
176+ tuple [
177+ tuple [
178+ TopicName ,
179+ tuple [
180+ tuple [
181+ Offset ,
182+ dict [bytes , bytes ],
183+ ],
184+ ...,
185+ ],
186+ ],
187+ ...,
188+ ],
189+ ]:
190+ async def xautoclaim () -> tuple [
191+ tuple [TopicName , tuple [tuple [Offset , dict [bytes , bytes ]], ...]], ...
192+ ]:
193+ stream_message = await client .xautoclaim (
194+ name = self .stream_sub .name ,
195+ groupname = self .stream_sub .group ,
196+ consumername = self .stream_sub .consumer ,
197+ min_idle_time = self .min_idle_time ,
198+ start_id = self .autoclaim_start_id ,
199+ count = 1 ,
200+ )
201+ stream_name = self .stream_sub .name .encode ()
202+ (next_id , messages , _ ) = stream_message
203+ # Update start_id for next call
204+ self .autoclaim_start_id = (
205+ next_id .decode () if isinstance (next_id , bytes ) else next_id
206+ )
207+ if not messages :
208+ return ()
209+ return ((stream_name , messages ),)
210+
211+ return xautoclaim ()
212+
167213 await super ().start (read )
168214
169215 @override
@@ -175,17 +221,43 @@ async def get_one(
175221 assert not self .calls , (
176222 "You can't use `get_one` method if subscriber has registered handlers."
177223 )
224+ if self .min_idle_time is None :
225+ if self .stream_sub .group and self .stream_sub .consumer :
226+ stream_message = await self ._client .xreadgroup (
227+ groupname = self .stream_sub .group ,
228+ consumername = self .stream_sub .consumer ,
229+ streams = {self .stream_sub .name : self .last_id },
230+ block = math .ceil (timeout * 1000 ),
231+ count = 1 ,
232+ )
233+ else :
234+ stream_message = await self ._client .xread (
235+ {self .stream_sub .name : self .last_id },
236+ block = math .ceil (timeout * 1000 ),
237+ count = 1 ,
238+ )
239+ if not stream_message :
240+ return None
178241
179- stream_message = await self ._client .xread (
180- {self .stream_sub .name : self .last_id },
181- block = math .ceil (timeout * 1000 ),
182- count = 1 ,
183- )
184-
185- if not stream_message :
186- return None
187-
188- ((stream_name , ((message_id , raw_message ),)),) = stream_message
242+ ((stream_name , ((message_id , raw_message ),)),) = stream_message
243+ else :
244+ stream_message = await self ._client .xautoclaim (
245+ name = self .stream_sub .name ,
246+ groupname = self .stream_sub .group ,
247+ consumername = self .stream_sub .consumer ,
248+ min_idle_time = self .min_idle_time ,
249+ start_id = self .autoclaim_start_id ,
250+ count = 1 ,
251+ )
252+ (next_id , messages , _ ) = stream_message
253+ # Update start_id for next call
254+ self .autoclaim_start_id = (
255+ next_id .decode () if isinstance (next_id , bytes ) else next_id
256+ )
257+ if not messages :
258+ return None
259+ stream_name = self .stream_sub .name .encode ()
260+ ((message_id , raw_message ),) = messages
189261
190262 self .last_id = message_id .decode ()
191263
@@ -221,16 +293,43 @@ async def __aiter__(self) -> AsyncIterator["RedisStreamMessage"]: # type: ignor
221293 async_parser , async_decoder = self ._get_parser_and_decoder ()
222294
223295 while True :
224- stream_message = await self ._client .xread (
225- {self .stream_sub .name : self .last_id },
226- block = math .ceil (timeout * 1000 ),
227- count = 1 ,
228- )
229-
230- if not stream_message :
231- continue
232-
233- ((stream_name , ((message_id , raw_message ),)),) = stream_message
296+ if self .min_idle_time is None :
297+ if self .stream_sub .group and self .stream_sub .consumer :
298+ stream_message = await self ._client .xreadgroup (
299+ groupname = self .stream_sub .group ,
300+ consumername = self .stream_sub .consumer ,
301+ streams = {self .stream_sub .name : self .last_id },
302+ block = math .ceil (timeout * 1000 ),
303+ count = 1 ,
304+ )
305+ else :
306+ stream_message = await self ._client .xread (
307+ {self .stream_sub .name : self .last_id },
308+ block = math .ceil (timeout * 1000 ),
309+ count = 1 ,
310+ )
311+ if not stream_message :
312+ continue
313+
314+ ((stream_name , ((message_id , raw_message ),)),) = stream_message
315+ else :
316+ stream_message = await self ._client .xautoclaim (
317+ name = self .stream_sub .name ,
318+ groupname = self .stream_sub .group ,
319+ consumername = self .stream_sub .consumer ,
320+ min_idle_time = self .min_idle_time ,
321+ start_id = self .autoclaim_start_id ,
322+ count = 1 ,
323+ )
324+ (next_id , messages , _ ) = stream_message
325+ # Update start_id for next call
326+ self .autoclaim_start_id = (
327+ next_id .decode () if isinstance (next_id , bytes ) else next_id
328+ )
329+ if not messages :
330+ continue
331+ stream_name = self .stream_sub .name .encode ()
332+ ((message_id , raw_message ),) = messages
234333
235334 self .last_id = message_id .decode ()
236335
0 commit comments