|
| 1 | +"""Utilities for waiting for conditions to be met. |
| 2 | +
|
| 3 | +This module provides utilities for polling functions until they return |
| 4 | +a truthy value or a timeout expires, useful for testing and development |
| 5 | +scenarios where you need to wait for system state changes. |
| 6 | +""" |
| 7 | + |
1 | 8 | import logging |
2 | 9 | import time |
3 | 10 |
|
|
6 | 13 |
|
7 | 14 | # pylint: disable=R0913 |
8 | 15 | def wait_for(func, timeout, first=0.0, step=1.0, text=None, args=None, kwargs=None): |
9 | | - """ |
10 | | - Wait until func() evaluates to True. |
| 16 | + """Wait until a function returns a truthy value or timeout expires. |
| 17 | +
|
| 18 | + This function repeatedly calls a given function with optional arguments |
| 19 | + until it returns a truthy value (anything that evaluates to True in a |
| 20 | + boolean context) or until the specified timeout expires. It provides |
| 21 | + configurable delays before the first attempt and between subsequent |
| 22 | + attempts, making it useful for polling operations in testing and |
| 23 | + development scenarios. |
| 24 | +
|
| 25 | + The function uses time.monotonic() for reliable timeout calculation that |
| 26 | + is not affected by system clock adjustments. Note that the step sleep |
| 27 | + duration is not interrupted when timeout expires, so actual elapsed time |
| 28 | + may exceed the specified timeout by up to one step duration. |
| 29 | +
|
| 30 | + :param func: Callable to be executed repeatedly until it returns a truthy |
| 31 | + value. Can be any callable object (function, lambda, method, |
| 32 | + callable class instance). |
| 33 | + :type func: callable |
| 34 | + :param timeout: Maximum time in seconds to wait for func to return a |
| 35 | + truthy value. Must be a non-negative number. If timeout |
| 36 | + expires before func returns truthy, None is returned. |
| 37 | + :type timeout: float or int |
| 38 | + :param first: Time in seconds to sleep before the first attempt to call |
| 39 | + func. Useful when you know the condition won't be met |
| 40 | + immediately. Defaults to 0.0 (no initial delay). |
| 41 | + :type first: float or int |
| 42 | + :param step: Time in seconds to sleep between successive calls to func. |
| 43 | + The actual sleep happens after each failed attempt. Defaults |
| 44 | + to 1.0 second. Note that this sleep is not interrupted when |
| 45 | + timeout expires. |
| 46 | + :type step: float or int |
| 47 | + :param text: Optional debug message to log before each attempt. When |
| 48 | + provided, logs at DEBUG level with elapsed time since start. |
| 49 | + If None, no logging occurs. Useful for debugging wait |
| 50 | + operations. |
| 51 | + :type text: str or None |
| 52 | + :param args: Optional list or tuple of positional arguments to pass to |
| 53 | + func on each call. If None, defaults to empty list. |
| 54 | + :type args: list, tuple, or None |
| 55 | + :param kwargs: Optional dictionary of keyword arguments to pass to func on |
| 56 | + each call. If None, defaults to empty dict. |
| 57 | + :type kwargs: dict or None |
| 58 | + :return: The truthy return value from func if it succeeds within timeout, |
| 59 | + or None if timeout expires without func returning a truthy value. |
| 60 | + The actual return value from func is preserved (e.g., strings, |
| 61 | + numbers, lists, objects). |
| 62 | + :rtype: Any (return type of func) or None |
| 63 | + :raises: Any exception raised by func will be propagated to the caller. |
| 64 | + No exception handling is performed on func calls. |
11 | 65 |
|
12 | | - If func() evaluates to True before timeout expires, return the |
13 | | - value of func(). Otherwise return None. |
| 66 | + Example:: |
14 | 67 |
|
15 | | - :param timeout: Timeout in seconds |
16 | | - :param first: Time to sleep before first attempt |
17 | | - :param step: Time to sleep between attempts in seconds |
18 | | - :param text: Text to print while waiting, for debug purposes |
19 | | - :param args: Positional arguments to func |
20 | | - :param kwargs: Keyword arguments to func |
| 68 | + >>> import os |
| 69 | + >>> # Wait for a file to exist |
| 70 | + >>> wait_for(lambda: os.path.exists("/tmp/myfile"), timeout=30, step=1) |
| 71 | + True |
| 72 | + >>> # Wait for a counter to reach threshold |
| 73 | + >>> counter = [0] |
| 74 | + >>> def check(): counter[0] += 1; return counter[0] >= 5 |
| 75 | + >>> wait_for(check, timeout=10, step=0.5) |
| 76 | + True |
| 77 | + >>> # Wait with custom function and arguments |
| 78 | + >>> def check_value(expected, current): |
| 79 | + ... return current >= expected |
| 80 | + >>> wait_for(check_value, timeout=5, step=0.1, args=[10, 15]) |
| 81 | + True |
| 82 | + >>> # Wait with debug logging |
| 83 | + >>> wait_for(lambda: False, timeout=2, step=0.5, text="Waiting for condition") |
| 84 | + None |
21 | 85 | """ |
22 | 86 | args = args or [] |
23 | 87 | kwargs = kwargs or {} |
|
0 commit comments