Skip to content

Commit

Permalink
Add retry_if_exception_message and complement
Browse files Browse the repository at this point in the history
The _predicate_factory was originally wrapped.

Wrapping made it more confusing. Easier to just have it
clearly be the inverted predicate.

Considered using an anti-pattern to set a dynamic name, but in reality
the method that is decorated should make it obvious if it's initialized
with message or match.

Add imports to init
  • Loading branch information
Brian-Williams committed Jul 18, 2018
1 parent 1fe4714 commit 3c7e03c
Show file tree
Hide file tree
Showing 3 changed files with 130 additions and 2 deletions.
2 changes: 2 additions & 0 deletions tenacity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@
from .retry import retry_if_result # noqa
from .retry import retry_never # noqa
from .retry import retry_unless_exception_type # noqa
from .retry import retry_if_exception_message # noqa
from .retry import retry_if_not_exception_message # noqa

# Import all nap strategies for easier usage.
from .nap import sleep # noqa
Expand Down
56 changes: 56 additions & 0 deletions tenacity/retry.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
# limitations under the License.

import abc
import re

import six

Expand Down Expand Up @@ -111,6 +112,61 @@ def __call__(self, attempt):
return not self.predicate(attempt.result())


class retry_if_exception_message(retry_base):
"""Retries if an exception message equals or matches."""

def __init__(self, message=None, match=None, require_exception=False):
if message and match:
raise TypeError(
"{}() takes either 'message' or 'match', not both".format(
self.__class__.__name__))
self.predicate = self._predicate_factory(message, match)
self.require_exception = require_exception

def _predicate_factory(self, message, match):
if message:
def message_fnc(exception_message):
return message == exception_message
return message_fnc
elif match:
prog = re.compile(match)

def match_fnc(exception_message):
return prog.match(str(exception_message))
return match_fnc
raise TypeError(
"{}() missing 1 required argument 'message' or 'match'".format(
self.__class__.__name__))

def _no_failure(self):
"""Retry logic for no failure."""
return False

def __call__(self, attempt):
if not attempt.failed:
if self.require_exception:
raise RuntimeError("No exception to get errormessage from")
return self._no_failure()
return self.predicate(attempt.exception().value)


class retry_if_not_exception_message(retry_if_exception_message):
"""Retries until an exception message equals or matches."""

def _predicate_factory(self, *args, **kwargs):
predicate = super(
retry_if_not_exception_message, self
)._predicate_factory(*args, **kwargs)

def inverted_predicate(*args_, **kwargs_):
return not predicate(*args_, **kwargs_)

return inverted_predicate

def _no_failure(self):
return True


class retry_any(retry_base):
"""Retries if any of the retries condition is valid."""

Expand Down
74 changes: 72 additions & 2 deletions tenacity/tests/test_tenacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,15 @@ def _r():
_r)
self.assertEqual(5, r.statistics['attempt_number'])

def test_retry_if_exception_message_negative_no_inputs(self):
with self.assertRaises(TypeError):
tenacity.retry_if_exception_message()

def test_retry_if_exception_message_negative_too_many_inputs(self):
with self.assertRaises(TypeError):
tenacity.retry_if_exception_message(
message="negative", match="negative")


class NoneReturnUntilAfterCount(object):
"""Holds counter state for invoking a method several times in a row."""
Expand Down Expand Up @@ -585,6 +594,8 @@ def __str__(self):
class NoCustomErrorAfterCount(object):
"""Holds counter state for invoking a method several times in a row."""

derived_message = "This is a Custom exception class"

def __init__(self, count):
self.counter = 0
self.count = count
Expand All @@ -596,8 +607,7 @@ def go(self):
"""
if self.counter < self.count:
self.counter += 1
derived_message = "This is a Custom exception class"
raise CustomError(derived_message)
raise CustomError(self.derived_message)
return True


Expand Down Expand Up @@ -657,6 +667,32 @@ def _retryable_test_with_unless_exception_type_no_input(thing):
return thing.go()


@retry(
stop=tenacity.stop_after_attempt(5),
retry=tenacity.retry_if_exception_message(
message=NoCustomErrorAfterCount.derived_message))
def _retryable_test_if_exception_message_message(thing):
return thing.go()


@retry(retry=tenacity.retry_if_not_exception_message(
message=NoCustomErrorAfterCount.derived_message))
def _retryable_test_if_not_exception_message_message(thing):
return thing.go()


@retry(retry=tenacity.retry_if_exception_message(
match=NoCustomErrorAfterCount.derived_message[:3] + ".*"))
def _retryable_test_if_exception_message_match(thing):
return thing.go()


@retry(retry=tenacity.retry_if_not_exception_message(
match=NoCustomErrorAfterCount.derived_message[:3] + ".*"))
def _retryable_test_if_not_exception_message_match(thing):
return thing.go()


@retry
def _retryable_default(thing):
return thing.go()
Expand Down Expand Up @@ -773,6 +809,40 @@ def test_retry_until_exception_of_type_wrong_exception(self):
self.assertTrue(isinstance(e, RetryError))
print(e)

def test_retry_if_exception_message(self):
try:
self.assertTrue(_retryable_test_if_exception_message_message(
NoCustomErrorAfterCount(3)))
except CustomError:
print(_retryable_test_if_exception_message_message.retry.
statistics)
self.fail("CustomError should've been retried from errormessage")

def test_retry_if_not_exception_message(self):
try:
self.assertTrue(_retryable_test_if_not_exception_message_message(
NoCustomErrorAfterCount(2)))
except CustomError:
s = _retryable_test_if_not_exception_message_message.retry.\
statistics
self.assertTrue(s['attempt_number'] == 1)

def test_retry_if_exception_message_match(self):
try:
self.assertTrue(_retryable_test_if_exception_message_match(
NoCustomErrorAfterCount(3)))
except CustomError:
self.fail("CustomError should've been retried from errormessage")

def test_retry_if_not_exception_message_match(self):
try:
self.assertTrue(_retryable_test_if_not_exception_message_message(
NoCustomErrorAfterCount(2)))
except CustomError:
s = _retryable_test_if_not_exception_message_message.retry.\
statistics
self.assertTrue(s['attempt_number'] == 1)

def test_defaults(self):
self.assertTrue(_retryable_default(NoNameErrorAfterCount(5)))
self.assertTrue(_retryable_default_f(NoNameErrorAfterCount(5)))
Expand Down

0 comments on commit 3c7e03c

Please sign in to comment.