userver: /data/code/userver/testsuite/pytest_plugins/pytest_userver/utils/sync.py Source File
Loading...
Searching...
No Matches
sync.py
1import asyncio
2from collections.abc import Callable
3import datetime
4import inspect
5import logging
6from typing import Any
7
8TOTAL_WAIT_SECONDS = 30
9ITERATION_PERIOD_SECONDS = 0.5
10
11logger = logging.getLogger(__name__)
12
13
14class NotReady(Exception):
15 pass
16
17
18async def wait_until(
19 func: Callable,
20 *,
21 relax_period_seconds: float = ITERATION_PERIOD_SECONDS,
22 total_wait_seconds: float = TOTAL_WAIT_SECONDS,
23) -> Any:
24 """
25 Waits for some external event for `total_wait_seconds` and
26 calls `func` every `relax_period_seconds`.
27 If `func` raises NotReady exception, the waiting continues.
28 If `func` succesfully returns, wait_until() returns the same value.
29 After `total_wait_seconds` of unsuccesfull checks wait_until()
30 raises TimeoutError.
31
32 Example:
33
34 .. code-block:: python
35
36 async def try_to_connect_db():
37 if not db_conn_is_ok():
38 raise NotReady()
39
40 await wait_until(try_to_connect_db)
41
42 @throws TimeoutError after `total_wait_seconds` of unsuccesfull checks
43 @note If possible, use @ref testpoint instead. @ref testpoint is
44 an explicit message from the server "I'm ready", while wait_until()
45 uses an implicit idea "A condition is met, so I can continue".
46
47 @ingroup userver_testsuite
48 """
49 start = datetime.datetime.now()
50 while datetime.datetime.now() - start < datetime.timedelta(seconds=total_wait_seconds):
51 try:
52 return await func()
53 except NotReady:
54 await asyncio.sleep(relax_period_seconds)
55
56 raise TimeoutError()
57
58
59async def wait(
60 check: Callable,
61 *,
62 catch: Any | tuple = (),
63 relax_period_seconds: float = ITERATION_PERIOD_SECONDS,
64 total_wait_seconds: float = TOTAL_WAIT_SECONDS,
65 failure_msg: str = 'Timeout happened while waiting for an event',
66 relax_msg: str | None = None,
67) -> None:
68 """
69 Waits for some external event for `total_wait_seconds` and
70 calls `check` every `relax_period_seconds`.
71 If `check` returns False or raises NotReady exception
72 (or any exception type referenced in `catch`), the waiting continues.
73 If `check` returns True, wait_until() stops and returns.
74 After `total_wait_seconds` of unsuccesfull checks wait_until()
75 raises TimeoutError.
76
77 Example:
78
79 .. code-block:: python
80
81 async def is_ready():
82 return await conn.is_ready()
83
84 await wait_until(try_to_connect_db)
85
86 @throws TimeoutError after `total_wait_seconds` of unsuccesfull checks
87 @note If possible, use @ref testpoint instead. @ref testpoint is
88 an explicit message from the server "I'm ready", while wait_until()
89 uses an implicit idea "A condition is met, so I can continue".
90
91 @ingroup userver_testsuite
92 """
93
94 if isinstance(catch, tuple):
95 exceptions = catch
96 else:
97 exceptions = (catch,)
98 exceptions = (NotReady, *exceptions)
99
100 async def func():
101 try:
102 result = check()
103 if inspect.isawaitable(result):
104 ok = await result
105 else:
106 ok = result
107 if ok:
108 return
109 except exceptions:
110 pass
111
112 logger.warning('%s', relax_msg)
113 raise NotReady from None
114
115 try:
116 await wait_until(
117 func,
118 relax_period_seconds=relax_period_seconds,
119 total_wait_seconds=total_wait_seconds,
120 )
121 except TimeoutError:
122 assert False, failure_msg