diff --git a/README.md b/README.md index 31d1f32..2cd53ff 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,19 @@ You can similarly auto-capture exceptions using `resultify(...)`. Please note th Err(TypeError()) ``` + +You can `retry` a function that returns a `Result` type with a constant backoff. + +``` +>>> from resultify import resultify, retry +... @retry(retries=2, delay=2, initial_delay=1): +... @resultify(Exception) +... def foo(): +... # do something that needs retrying here +``` + +This example waits 1 second before executing the initial call, then attempts the initial call, then executes two retries, spaces out two seconds from the previous call. If any execution was a success, the `Ok` value will be returned. If the retries were exhausted and no `Ok` was returned, we return the `Err` value. + Since documentation always lies, please refer to the unit tests for examples of usage. diff --git a/resultify/__init__.py b/resultify/__init__.py index 769a94a..53a04c9 100644 --- a/resultify/__init__.py +++ b/resultify/__init__.py @@ -1,3 +1,4 @@ +from time import sleep from functools import wraps from typing import Any, Generic, TypeVar, Union, Type, Callable @@ -113,3 +114,25 @@ def inner(*args, **kwargs): return inner return decorator + + +def retry(retries: int = 0, delay: int = 0, initial_delay: int = 0): + def decorator( + function: Callable[..., Union[Ok[T], Err[E]]] + ) -> Callable[..., Union[Ok[T], Err[E]]]: + @wraps(function) + def func_with_retries(*args, **kwargs) -> Union[Ok[T], Err[E]]: + sleep(initial_delay) + _retries = retries + res: Union[Ok[T], Err[E]] = function(*args, **kwargs) + while _retries >= 1: + if res.is_ok(): + break + sleep(delay) + _retries -= 1 + res = function(*args, **kwargs) + return res + + return func_with_retries + + return decorator diff --git a/test/test_resultify.py b/test/test_resultify.py index abf613b..5d99300 100644 --- a/test/test_resultify.py +++ b/test/test_resultify.py @@ -2,7 +2,7 @@ import pytest -from resultify import Err, Ok, UnwrapError, resultify +from resultify import Err, Ok, UnwrapError, resultify, retry class TestOk: @@ -124,3 +124,50 @@ def test_isinstance_result_type(self): n = Err("nay") assert isinstance(o, (Ok, Err)) assert isinstance(n, (Ok, Err)) + + +class TestRetry: + def test_retry_fail(self): + class Config: + value = "asdfasdfasdf" + counter = 0 + failing_tries = 999 + retries = 2 + + config = Config() + + @retry(retries=config.retries) + @resultify(Exception) + def foo(): + config.counter += 1 + if config.counter <= config.failing_tries: + raise TypeError() + else: + return config.value + + x = foo() + assert isinstance(x, Err) + assert config.counter == 3 + + def test_retry_ok(self): + class Config: + value = "asdfasdfasdf" + counter = 0 + failing_tries = 2 + retries = 2 + + config = Config() + + @retry(retries=config.retries) + @resultify(Exception) + def foo(): + config.counter += 1 + if config.counter <= config.failing_tries: + raise TypeError() + else: + return config.value + + x = foo() + assert isinstance(x, Ok) + assert x.ok() == config.value + assert config.counter == 3