iripau.functools module¶
Function utilities with similar philosophy as the functools module.
- iripau.functools.wait_for(condition, *args, _timeout=None, _outcome=True, _poll_time=10, _stop_condition=None, **kwargs)[source]¶
Wait for
condition(*args, **kwargs)to be_outcome.- Parameters:
condition (
Callable) – Call this function many times with*argsand**kwargsuntil its return value is_outcome.*args – Pass these arguments to
condition.**kwargs – Pass these keyword arguments to
condition._timeout (
float) – Max time in seconds to continue on callingcondition._outcome (
bool) – The desired outcome to gracefully stop waiting._poll_time (
float) – Seconds in betweenconditioncalls._stop_condition (
Callable[[],bool]) – Stop waiting if the result of calling this function isTrue.
- Raises:
TimeoutError – If timeout reached.
InterruptedError – If
_stop_condition()returnsTrue.
Example
Wait for a network interface to have an IP address:
import netifaces def iface_is_connected(iface): """ ``iface`` has an IP v4 address """ return netifaces.AF_INET in netifaces.ifaddresses(iface) wait_for(iface_is_connected, "eth0", _timeout=10)
Example
Wait for a process to finish, but stop waiting if there is an error log:
import psutil def log_file_contains_error(): """ The log informs the process has stalled """ with open("/tmp/events.log") as f: return "Error: Process stalled" in f.read() wait_for( psutil.pid_exists, 12345, _outcome=False, _timeout=3600, _poll_time=600, _stop_condition=log_file_contains_error )
Tip
Create
conditionand_stop_conditionfunctions that read nicely when used in if statements, not in questions. For example, preferhost_is_reachable()instead ofis_host_reachable().
- iripau.functools.retry(tries, exceptions=<class 'Exception'>, retry_condition=None, backoff_time=0)[source]¶
Add retry capabilities to the decorated function.
Call the decorated function up to
triestimes, until it succeeds or it raises an unexpected exception. The decorated function Will only be retried if it raises one of the expectedexceptionsandretry_conditionis fulfilled. Any other exception Will be raised immediately without further tries.- Parameters:
tries (
int) – The decorated function will be called at most this amount of times.exceptions (
Union[Exception,Tuple[Exception]]) – The expected exceptions to trigger the retry.retry_condition (
Callable[[Exception],bool]) – Function to call with the caught exception to verify if the retry process should continue. If it returnsFalse, the caught exception will be raised.backoff_time (
int) – Seconds to wait before the next try.
- Return type:
Callable[[Callable[...,Any]],Callable[...,Any]]- Returns:
The decorator.
Example
Try to get the status of a server through a request to one of its REST API endpoints. Retry up to five times if Forbidden 403:
import requests from urllib.parse import urljoin from requests.exceptions import HTTPError def http_code_is_forbidden(e): """ The status code in the response is 403 """ return 403 == e.response.status_code @retry(tries=5, exceptions=HTTPError, retry_condition=http_code_is_forbidden) def get_server_status(server, headers): response = requests.get(urljoin(server, "api/status"), headers=headers) response.raise_for_status() # Maybe HTTPError return response.json()
Example
Find a line with a specific content in a log file. Retry if the file has not been created yet or if the content could not be found:
@retry(tries=3, exceptions=(FileNotFoundError, StopIteration)) def get_log_entry(path, content): with open(path) as log: # Maybe FileNotFoundError return next(line for line in log if content in line) # Maybe StopIteration
Example
Get information of a book through a REST API endpoint. Retry if the book does not exist, but fail immediately if the response JSON does not contain the key
"book":import requests @retry(tries=3, exceptions=KeyError) def get_book_data(book): response = requests.get("https://localhost:8080/api/books") response.raise_for_status() books = response.json()["books"] # Maybe KeyError yield # Only retry if exceptions occur after this line return books[name] # Maybe KeyError
- iripau.functools.globalize(function)[source]¶
Make
functionglobally available in the module it was defined so it can be serialized. Useful when calling local functions withmultiprocessing.- Parameters:
function (
Callable[...,Any]) – Function to make global.- Return type:
Callable[...,Any]- Returns:
The decorated function.
Example
Use a nested function in some processes with
multiprocessing:import multiprocessing def increase_multiple_processes(values, increment): @globalize def increase(x): return x + increment with multiprocessing.Pool(processes=3) as pool: return pool.map(increase, values)