Skip to content

Commit

Permalink
i see a red door and i want it...
Browse files Browse the repository at this point in the history
  • Loading branch information
Anvil committed Jun 8, 2024
1 parent ba10984 commit c591b3e
Show file tree
Hide file tree
Showing 4 changed files with 190 additions and 109 deletions.
92 changes: 59 additions & 33 deletions kaioretry/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,27 @@
from collections.abc import Callable
from mypy_extensions import DefaultNamedArg, DefaultArg

from .types import Exceptions, NonNegative, Number, Jitter, \
FuncParam, FuncRetVal, UpdateDelayFunc, JitterTuple, AioretryProtocol
from .types import (
Exceptions,
NonNegative,
Number,
Jitter,
FuncParam,
FuncRetVal,
UpdateDelayFunc,
JitterTuple,
AioretryProtocol,
)
from .context import Context
from .decorator import Retry


__version__ = "1.0.2"


RETRY_PARAMS_DOCSTRING: Final[str] = """
RETRY_PARAMS_DOCSTRING: Final[
str
] = """
:param exceptions: exceptions classes that will trigger another
try. Other exceptions raised by the decorated function will
not trigger a retry. The value of the exceptions parameters
Expand Down Expand Up @@ -54,17 +65,21 @@
"""


def _make_decorator(func: Callable[[Retry], FuncRetVal]) \
-> Callable[[
DefaultArg(Exceptions, 'exceptions'), # noqa: F821
DefaultArg(int, 'tries'), # noqa: F821
DefaultNamedArg(NonNegative, 'delay'), # noqa: F821
DefaultNamedArg(Number, 'backoff'), # noqa: F821
DefaultNamedArg(Jitter, 'jitter'), # noqa: F821
DefaultNamedArg(NonNegative | None, 'max_delay'), # noqa: F821
DefaultNamedArg(NonNegative, 'min_delay'), # noqa: F821
DefaultNamedArg(logging.Logger, 'logger')], # noqa: F821
FuncRetVal]:
def _make_decorator(
func: Callable[[Retry], FuncRetVal]
) -> Callable[
[
DefaultArg(Exceptions, "exceptions"), # noqa: F821
DefaultArg(int, "tries"), # noqa: F821
DefaultNamedArg(NonNegative, "delay"), # noqa: F821
DefaultNamedArg(Number, "backoff"), # noqa: F821
DefaultNamedArg(Jitter, "jitter"), # noqa: F821
DefaultNamedArg(NonNegative | None, "max_delay"), # noqa: F821
DefaultNamedArg(NonNegative, "min_delay"), # noqa: F821
DefaultNamedArg(logging.Logger, "logger"),
], # noqa: F821
FuncRetVal,
]:
"""Create a function that will accept a bunch of parameters and
create the matching :py:class:`Retry` and :py:class:`Context`
objects, in order to be compatible with the origin retry module.
Expand All @@ -82,48 +97,59 @@ def _make_decorator(func: Callable[[Retry], FuncRetVal]) \

# pylint: disable=too-many-arguments
def decoration(
exceptions: Exceptions = Exception, tries: int = -1, *,
delay: NonNegative = 0, backoff: Number = 1,
jitter: Jitter = 0, max_delay: NonNegative | None = None,
min_delay: NonNegative = 0,
logger: logging.Logger = Retry.DEFAULT_LOGGER) \
-> FuncRetVal:

exceptions: Exceptions = Exception,
tries: int = -1,
*,
delay: NonNegative = 0,
backoff: Number = 1,
jitter: Jitter = 0,
max_delay: NonNegative | None = None,
min_delay: NonNegative = 0,
logger: logging.Logger = Retry.DEFAULT_LOGGER,
) -> FuncRetVal:
if isinstance(jitter, (int, float)):
jitter_f = cast(UpdateDelayFunc, jitter.__add__)
elif isinstance(jitter, (tuple, list)):

def jitter_f(delay: Number) -> Number:
return random.uniform(*cast(JitterTuple, jitter)) + delay

else:
raise TypeError("jitter parameter is neither a number "
f"nor a 2 length tuple: {jitter}")
raise TypeError(
"jitter parameter is neither a number "
f"nor a 2 length tuple: {jitter}"
)

def update_delay(delay: NonNegative) -> NonNegative:
return jitter_f(delay * backoff)

context = Context(
tries=tries, delay=delay, update_delay=update_delay,
max_delay=max_delay, min_delay=min_delay, logger=logger)
retry_obj = Retry(
exceptions=exceptions, context=context, logger=logger)
tries=tries,
delay=delay,
update_delay=update_delay,
max_delay=max_delay,
min_delay=min_delay,
logger=logger,
)
retry_obj = Retry(exceptions=exceptions, context=context, logger=logger)
return func(retry_obj)

# No need to override module
decoration.__name__ = func.__name__
decoration.__qualname__ = func.__qualname__

# Override return value annotation but not parameters.
decoration.__annotations__['return'] = func.__annotations__['return']
decoration.__annotations__["return"] = func.__annotations__["return"]

if func.__doc__ is not None: # pragma: nocover
decoration.__doc__ = func.__doc__.replace(
"%PARAMS%", RETRY_PARAMS_DOCSTRING)
decoration.__doc__ = func.__doc__.replace("%PARAMS%", RETRY_PARAMS_DOCSTRING)
return decoration


@_make_decorator
def retry(retry_obj: Retry) -> Callable[[Callable[FuncParam, FuncRetVal]],
Callable[FuncParam, FuncRetVal]]:
def retry(
retry_obj: Retry,
) -> Callable[[Callable[FuncParam, FuncRetVal]], Callable[FuncParam, FuncRetVal]]:
"""Return a new retry decorator, suitable for regular functions. Functions
decorated will transparently retry when a exception is raised.
Expand Down Expand Up @@ -157,4 +183,4 @@ def aioretry(retry_obj: Retry) -> AioretryProtocol:
return retry_obj.aioretry


__all__ = ['Retry', 'Context', 'retry', 'aioretry']
__all__ = ["Retry", "Context", "retry", "aioretry"]
60 changes: 41 additions & 19 deletions kaioretry/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@
from .types import NonNegative, Number, UpdateDelayFunc


SleepRetVal = TypeVar('SleepRetVal', None, Awaitable[None])
SleepRetVal = TypeVar("SleepRetVal", None, Awaitable[None])
SleepF = Callable[[Number], SleepRetVal]


Expand All @@ -108,9 +108,16 @@ class _ContextIterator(Generic[SleepRetVal]):

# pylint: disable=too-few-public-methods

def __init__(self, identifier: uuid.UUID, sleep: SleepF[Any], tries: int,
delay: NonNegative, update_delay: UpdateDelayFunc,
logger: logging.Logger, /) -> None:
def __init__(
self,
identifier: uuid.UUID,
sleep: SleepF[Any],
tries: int,
delay: NonNegative,
update_delay: UpdateDelayFunc,
logger: logging.Logger,
/,
) -> None:
# pylint: disable=too-many-arguments
self.__identifier = identifier
self.__sleep = sleep
Expand Down Expand Up @@ -188,30 +195,37 @@ class Context:
are provided to the constructor.
"""

def __init__(self, /, tries: int = -1, delay: NonNegative = 0, *,
update_delay: UpdateDelayFunc = lambda value: value,
max_delay: NonNegative | None = None,
min_delay: NonNegative = 0,
logger: logging.Logger = DEFAULT_LOGGER) -> None:
def __init__(
self,
/,
tries: int = -1,
delay: NonNegative = 0,
*,
update_delay: UpdateDelayFunc = lambda value: value,
max_delay: NonNegative | None = None,
min_delay: NonNegative = 0,
logger: logging.Logger = DEFAULT_LOGGER,
) -> None:
# pylint: disable=too-many-arguments
if tries == 0:
raise ValueError("tries value cannot be 0")
self.__tries = tries
self.__delay = delay
self.__update_delay_value = update_delay
if min_delay < 0:
raise ValueError(
f"min_delay cannot be less than 0. ({min_delay} given)")
raise ValueError(f"min_delay cannot be less than 0. ({min_delay} given)")
self.__min_delay = min_delay
if max_delay is not None and max_delay < min_delay:
raise ValueError(
"min_delay cannot be greater than max_delay. "
f"min given: {min_delay}. max given: {max_delay}")
f"min given: {min_delay}. max given: {max_delay}"
)
self.__max_delay = max_delay
self.__str = (
f"{self.__class__.__name__}("
f"tries={tries}, "
f"delay=({min_delay}<={delay}<={max_delay}))")
f"delay=({min_delay}<={delay}<={max_delay}))"
)
self.__logger = logger

def __update_delay(self, delay: NonNegative) -> NonNegative:
Expand All @@ -227,11 +241,19 @@ def __update_delay(self, delay: NonNegative) -> NonNegative:
delay = max(delay, self.__min_delay)
return delay

def __make_iterator(self, sleep: SleepF[SleepRetVal]) -> Generator[
SleepRetVal, None, None]:
return iter(_ContextIterator(
uuid.uuid4(), sleep, self.__tries, self.__delay,
self.__update_delay, self.__logger))
def __make_iterator(
self, sleep: SleepF[SleepRetVal]
) -> Generator[SleepRetVal, None, None]:
return iter(
_ContextIterator(
uuid.uuid4(),
sleep,
self.__tries,
self.__delay,
self.__update_delay,
self.__logger,
)
)

def __iter__(self) -> Generator[None, None, None]:
"""Returns a generator that perform sleep (using regular
Expand All @@ -257,4 +279,4 @@ def __str__(self) -> str:
return self.__str


__all__ = ['Context']
__all__ = ["Context"]
Loading

0 comments on commit c591b3e

Please sign in to comment.