77import asyncio
88import datetime
99from collections .abc import Awaitable , Callable
10- from contextlib import AbstractAsyncContextManager
1110from datetime import timedelta
1211from itertools import chain
1312from unittest .mock import Mock
2423)
2524from servicelib .redis ._errors import LockLostError
2625from servicelib .utils import logged_gather
27- from settings_library .redis import RedisDatabase
2826from tenacity .asyncio import AsyncRetrying
2927from tenacity .retry import retry_if_exception_type
3028from tenacity .stop import stop_after_delay
@@ -43,11 +41,6 @@ async def _is_locked(redis_client_sdk: RedisClientSDK, lock_name: str) -> bool:
4341 return await lock .locked ()
4442
4543
46- @pytest .fixture
47- def lock_name (faker : Faker ) -> str :
48- return faker .pystr ()
49-
50-
5144def _exclusive_sleeping_task (
5245 redis_client_sdk : RedisClientSDK | Callable [..., RedisClientSDK ],
5346 lock_name : str | Callable [..., str ],
@@ -69,39 +62,33 @@ async def _() -> float:
6962
7063@pytest .fixture
7164def sleep_duration (faker : Faker ) -> float :
72- return faker .pyfloat (positive = True , min_value = 0.2 , max_value = 0.8 )
65+ return faker .pyfloat (min_value = 0.2 , max_value = 0.8 )
7366
7467
7568async def test_exclusive_with_empty_lock_key_raises (redis_client_sdk : RedisClientSDK ):
76- @exclusive (redis_client_sdk , lock_key = "" )
77- async def _ ():
78- pass
79-
8069 with pytest .raises (ValueError , match = "lock_key cannot be empty" ):
81- await _ ()
70+
71+ @exclusive (redis_client_sdk , lock_key = "" )
72+ async def _ ():
73+ pass
8274
8375
8476async def test_exclusive_decorator (
85- get_redis_client_sdk : Callable [
86- [RedisDatabase ], AbstractAsyncContextManager [RedisClientSDK ]
87- ],
77+ redis_client_sdk : RedisClientSDK ,
8878 lock_name : str ,
8979 sleep_duration : float ,
9080):
91- async with get_redis_client_sdk (RedisDatabase .RESOURCES ) as redis_client :
92- for _ in range (3 ):
93- assert (
94- await _exclusive_sleeping_task (
95- redis_client , lock_name , sleep_duration
96- )()
97- == sleep_duration
98- )
81+ for _ in range (3 ):
82+ assert (
83+ await _exclusive_sleeping_task (
84+ redis_client_sdk , lock_name , sleep_duration
85+ )()
86+ == sleep_duration
87+ )
9988
10089
10190async def test_exclusive_decorator_with_key_builder (
102- get_redis_client_sdk : Callable [
103- [RedisDatabase ], AbstractAsyncContextManager [RedisClientSDK ]
104- ],
91+ redis_client_sdk : RedisClientSDK ,
10592 lock_name : str ,
10693 sleep_duration : float ,
10794):
@@ -110,72 +97,62 @@ def _get_lock_name(*args, **kwargs) -> str:
11097 assert kwargs is not None
11198 return lock_name
11299
113- async with get_redis_client_sdk (RedisDatabase .RESOURCES ) as redis_client :
114- for _ in range (3 ):
115- assert (
116- await _exclusive_sleeping_task (
117- redis_client , _get_lock_name , sleep_duration
118- )()
119- == sleep_duration
120- )
100+ for _ in range (3 ):
101+ assert (
102+ await _exclusive_sleeping_task (
103+ redis_client_sdk , _get_lock_name , sleep_duration
104+ )()
105+ == sleep_duration
106+ )
121107
122108
123109async def test_exclusive_decorator_with_client_builder (
124- get_redis_client_sdk : Callable [
125- [RedisDatabase ], AbstractAsyncContextManager [RedisClientSDK ]
126- ],
110+ redis_client_sdk : RedisClientSDK ,
127111 lock_name : str ,
128112 sleep_duration : float ,
129113):
130- async with get_redis_client_sdk (RedisDatabase .RESOURCES ) as redis_client :
131-
132- def _get_redis_client_builder (* args , ** kwargs ) -> RedisClientSDK :
133- assert args is not None
134- assert kwargs is not None
135- return redis_client
136-
137- for _ in range (3 ):
138- assert (
139- await _exclusive_sleeping_task (
140- _get_redis_client_builder , lock_name , sleep_duration
141- )()
142- == sleep_duration
143- )
114+ def _get_redis_client_builder (* args , ** kwargs ) -> RedisClientSDK :
115+ assert args is not None
116+ assert kwargs is not None
117+ return redis_client_sdk
118+
119+ for _ in range (3 ):
120+ assert (
121+ await _exclusive_sleeping_task (
122+ _get_redis_client_builder , lock_name , sleep_duration
123+ )()
124+ == sleep_duration
125+ )
144126
145127
146128async def _acquire_lock_and_exclusively_sleep (
147- get_redis_client_sdk : Callable [
148- [RedisDatabase ], AbstractAsyncContextManager [RedisClientSDK ]
149- ],
129+ redis_client_sdk : RedisClientSDK ,
150130 lock_name : str | Callable [..., str ],
151131 sleep_duration : float ,
152132) -> None :
153- async with get_redis_client_sdk (RedisDatabase .RESOURCES ) as redis_client_sdk :
154- redis_lock_name = lock_name () if callable (lock_name ) else lock_name
133+ redis_lock_name = lock_name () if callable (lock_name ) else lock_name
155134
156- @exclusive (redis_client_sdk , lock_key = lock_name )
157- async def _ () -> float :
158- assert await _is_locked (redis_client_sdk , redis_lock_name )
159- await asyncio .sleep (sleep_duration )
160- assert await _is_locked (redis_client_sdk , redis_lock_name )
161- return sleep_duration
135+ @exclusive (redis_client_sdk , lock_key = lock_name )
136+ async def _ () -> float :
137+ assert await _is_locked (redis_client_sdk , redis_lock_name )
138+ await asyncio .sleep (sleep_duration )
139+ assert await _is_locked (redis_client_sdk , redis_lock_name )
140+ return sleep_duration
162141
163- assert await _ () == sleep_duration
142+ assert await _ () == sleep_duration
164143
165- assert not await _is_locked (redis_client_sdk , redis_lock_name )
144+ assert not await _is_locked (redis_client_sdk , redis_lock_name )
166145
167146
168147async def test_exclusive_parallel_lock_is_released_and_reacquired (
169- get_redis_client_sdk : Callable [
170- [RedisDatabase ], AbstractAsyncContextManager [RedisClientSDK ]
171- ],
148+ redis_client_sdk : RedisClientSDK ,
172149 lock_name : str ,
173150):
174151 parallel_tasks = 10
175152 results = await logged_gather (
176153 * [
177154 _acquire_lock_and_exclusively_sleep (
178- get_redis_client_sdk , lock_name , sleep_duration = 1
155+ redis_client_sdk , lock_name , sleep_duration = 1
179156 )
180157 for _ in range (parallel_tasks )
181158 ],
@@ -187,8 +164,7 @@ async def test_exclusive_parallel_lock_is_released_and_reacquired(
187164 ) == parallel_tasks - 1
188165
189166 # check lock is released
190- async with get_redis_client_sdk (RedisDatabase .RESOURCES ) as redis_client_sdk :
191- assert not await _is_locked (redis_client_sdk , lock_name )
167+ assert not await _is_locked (redis_client_sdk , lock_name )
192168
193169
194170async def _sleep_task (sleep_interval : float , on_sleep_events : Mock ) -> None :
@@ -211,39 +187,34 @@ async def _assert_on_sleep_done(on_sleep_events: Mock, *, stop_after: float):
211187
212188
213189async def _assert_task_completes_once (
214- get_redis_client_sdk : Callable [
215- [RedisDatabase ], AbstractAsyncContextManager [RedisClientSDK ]
216- ],
190+ redis_client_sdk : RedisClientSDK ,
217191 stop_after : float ,
218192) -> tuple [float , ...]:
219- async with get_redis_client_sdk (RedisDatabase .RESOURCES ) as redis_client_sdk :
220- sleep_events = Mock ()
221-
222- started_task = start_exclusive_periodic_task (
223- redis_client_sdk ,
224- _sleep_task ,
225- task_period = timedelta (seconds = 1 ),
226- task_name = "pytest_sleep_task" ,
227- sleep_interval = 1 ,
228- on_sleep_events = sleep_events ,
229- )
193+ sleep_events = Mock ()
194+
195+ started_task = start_exclusive_periodic_task (
196+ redis_client_sdk ,
197+ _sleep_task ,
198+ task_period = timedelta (seconds = 1 ),
199+ task_name = "pytest_sleep_task" ,
200+ sleep_interval = 1 ,
201+ on_sleep_events = sleep_events ,
202+ )
230203
231- await _assert_on_sleep_done (sleep_events , stop_after = stop_after )
204+ await _assert_on_sleep_done (sleep_events , stop_after = stop_after )
232205
233- await cancel_wait_task (started_task , max_delay = 5 )
206+ await cancel_wait_task (started_task , max_delay = 5 )
234207
235- events_timestamps : tuple [float , ...] = tuple (
236- x .args [0 ].timestamp () for x in sleep_events .call_args_list
237- )
238- return events_timestamps
208+ events_timestamps : tuple [float , ...] = tuple (
209+ x .args [0 ].timestamp () for x in sleep_events .call_args_list
210+ )
211+ return events_timestamps
239212
240213
241214async def test_start_exclusive_periodic_task_single (
242- get_redis_client_sdk : Callable [
243- [RedisDatabase ], AbstractAsyncContextManager [RedisClientSDK ]
244- ],
215+ redis_client_sdk : RedisClientSDK ,
245216):
246- await _assert_task_completes_once (get_redis_client_sdk , stop_after = 2 )
217+ await _assert_task_completes_once (redis_client_sdk , stop_after = 2 )
247218
248219
249220def _check_elements_lower (lst : list ) -> bool :
@@ -260,14 +231,12 @@ def test__check_elements_lower():
260231
261232
262233async def test_start_exclusive_periodic_task_parallel_all_finish (
263- get_redis_client_sdk : Callable [
264- [RedisDatabase ], AbstractAsyncContextManager [RedisClientSDK ]
265- ],
234+ redis_client_sdk : RedisClientSDK ,
266235):
267236 parallel_tasks = 10
268237 results : list [tuple [float , float ]] = await logged_gather (
269238 * [
270- _assert_task_completes_once (get_redis_client_sdk , stop_after = 60 )
239+ _assert_task_completes_once (redis_client_sdk , stop_after = 60 )
271240 for _ in range (parallel_tasks )
272241 ],
273242 reraise = False ,
@@ -289,27 +258,21 @@ async def test_start_exclusive_periodic_task_parallel_all_finish(
289258
290259
291260async def test_exclusive_raises_if_lock_is_lost (
292- get_redis_client_sdk : Callable [
293- [RedisDatabase ], AbstractAsyncContextManager [RedisClientSDK ]
294- ],
295- faker : Faker ,
261+ redis_client_sdk : RedisClientSDK ,
262+ lock_name : str ,
296263):
297- lock_name = faker .pystr ()
298-
299264 started_event = asyncio .Event ()
300265
301- async with get_redis_client_sdk (RedisDatabase .RESOURCES ) as redis_client_sdk :
302-
303- @exclusive (redis_client_sdk , lock_key = lock_name )
304- async def _ (time_to_sleep : datetime .timedelta ) -> datetime .timedelta :
305- started_event .set ()
306- await asyncio .sleep (time_to_sleep .total_seconds ())
307- return time_to_sleep
308-
309- exclusive_task = asyncio .create_task (_ (datetime .timedelta (seconds = 10 )))
310- await asyncio .wait_for (started_event .wait (), timeout = 2 )
311- # let's simlulate lost lock by forcefully deleting it
312- await redis_client_sdk ._client .delete (lock_name ) # noqa: SLF001
313-
314- with pytest .raises (LockLostError ):
315- await exclusive_task
266+ @exclusive (redis_client_sdk , lock_key = lock_name )
267+ async def _ (time_to_sleep : datetime .timedelta ) -> datetime .timedelta :
268+ started_event .set ()
269+ await asyncio .sleep (time_to_sleep .total_seconds ())
270+ return time_to_sleep
271+
272+ exclusive_task = asyncio .create_task (_ (datetime .timedelta (seconds = 10 )))
273+ await asyncio .wait_for (started_event .wait (), timeout = 2 )
274+ # let's simlulate lost lock by forcefully deleting it
275+ await redis_client_sdk .redis .delete (lock_name )
276+
277+ with pytest .raises (LockLostError ):
278+ await exclusive_task
0 commit comments