Skip to content

retry_if_exception_message: use re.search for the match= pattern so it finds the regex anywhere in the message - #652

Open
HrachShah wants to merge 1 commit into
jd:mainfrom
HrachShah:fix/retry-if-exception-message-match-uses-search
Open

retry_if_exception_message: use re.search for the match= pattern so it finds the regex anywhere in the message#652
HrachShah wants to merge 1 commit into
jd:mainfrom
HrachShah:fix/retry-if-exception-message-match-uses-search

Conversation

@HrachShah

Copy link
Copy Markdown
Contributor

Bug

retry_if_exception_message(match=...) compiled the user's regex and called re.Pattern.match(), which only matches at the start of the string. The parameter is named match and the docstring just says "matches" — so users reasonably expect the pattern to be found anywhere in the exception message, the same way re.search does. With the current implementation, a user who wrote:

@retry(retry=retry_if_exception_message(match="rate limit"))
def fetch(): ...

got a retry on ValueError("rate limit hit") (works by accident) but no retry on ValueError("HTTP 429: rate limit hit") (pattern not at the start). That second case is the common real-world one and is exactly the kind of message HTTP libraries produce.

Fix

Use self.match.search(...) instead of self.match.match(...) in retry_if_exception_message._check. The behaviour of message= (full-string equality) is unchanged.

Test

Added test_retry_if_exception_message_match_finds_pattern_anywhere plus a module-level helper that raises CustomError("HTTP 429: rate limit exceeded") and matches on "limit exceeded" (which does not occur at position 0). The pre-existing test test_retry_if_exception_message_match still passes — it uses a pattern prefixed with derived_message[:3] + ".*" so both re.match and re.search accept it.

Full suite: 128 passed.

…t finds the regex anywhere in the exception message, not only at the start
Comment thread tests/test_tenacity.py
retry=tenacity.retry_if_exception_message(
message=NoCustomErrorAfterCount.derived_message
),
stop=tenacity.stop_after_attempt(5),

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to change that

@Mukller Mukller left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified locally against upstream master and this branch (Python 3.13):

The fix works and is well-targeted:

retry_if_exception_message(match="limit exceeded")
message = "HTTP 429: rate limit exceeded"

master  (re.match):  _check -> False   <- zero retries, the bug
branch  (re.search): _check -> True

tests/test_tenacity.py -k exception_message — 8/8 green on the branch, including the new regression test with a realistic message shape.

Compatibility surface, checked explicitly: anchored patterns behave identically (^HTTP 429 matches at offset 0 and rejects mid-message occurrences under both match() and search()), so users who consciously anchored are safe. The flip affects unanchored patterns that previously failed everywhere except position 0 — e.g. match="429" now matches "[OK] 429 done" where master returned False. For most users that's the semantics they intended all along (and what the parameter name suggests), but it can only be discovered as a sudden change in retry behavior.

Given that, two suggestions:

  1. Changelog entry should call this out as a behavior change, not just a fix — anyone auditing retries after upgrading will want this line.
  2. If maintainers are worried about the flip, an opt-in flag (search=True) would preserve old semantics; personally I think search-as-default matches user intent better and a major-version note suffices.

One tiny nit: the inline comment explains re.search vs re.match, but the same reasoning applies to the message= equality branch above it — no action needed, just noting the comment reads slightly like it covers _check as a whole.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants