Skip to content

Commit

Permalink
Preserve function default and kwdefault through retry decorator (#406)
Browse files Browse the repository at this point in the history
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
michael-motional and mergify[bot] committed Aug 21, 2023
1 parent a29f494 commit 3100582
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
---
fixes:
- |
Preserve __defaults__ and __kwdefaults__ through retry decorator
2 changes: 1 addition & 1 deletion tenacity/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ def wraps(self, f: WrappedFn) -> WrappedFn:
:param f: A function to wraps for retrying.
"""

@functools.wraps(f)
@functools.wraps(f, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__"))
def wrapped_f(*args: t.Any, **kw: t.Any) -> t.Any:
return self(f, *args, **kw)

Expand Down
2 changes: 1 addition & 1 deletion tenacity/_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def wraps(self, fn: WrappedFn) -> WrappedFn:
fn = super().wraps(fn)
# Ensure wrapper is recognized as a coroutine function.

@functools.wraps(fn)
@functools.wraps(fn, functools.WRAPPER_ASSIGNMENTS + ("__defaults__", "__kwdefaults__"))
async def async_wrapped(*args: t.Any, **kwargs: t.Any) -> t.Any:
return await fn(*args, **kwargs)

Expand Down
15 changes: 15 additions & 0 deletions tests/test_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

import pytest

import tenacity
from tenacity import AsyncRetrying, RetryError
from tenacity import _asyncio as tasyncio
from tenacity import retry, retry_if_result, stop_after_attempt
Expand Down Expand Up @@ -89,6 +90,20 @@ def test_retry_attributes(self):
assert hasattr(_retryable_coroutine, "retry")
assert hasattr(_retryable_coroutine, "retry_with")

def test_retry_preserves_argument_defaults(self):
async def function_with_defaults(a=1):
return a

async def function_with_kwdefaults(*, a=1):
return a

retrying = AsyncRetrying(wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3))
wrapped_defaults_function = retrying.wraps(function_with_defaults)
wrapped_kwdefaults_function = retrying.wraps(function_with_kwdefaults)

self.assertEqual(function_with_defaults.__defaults__, wrapped_defaults_function.__defaults__)
self.assertEqual(function_with_kwdefaults.__kwdefaults__, wrapped_kwdefaults_function.__kwdefaults__)

@asynctest
async def test_attempt_number_is_correct_for_interleaved_coroutines(self):
attempts = []
Expand Down
14 changes: 14 additions & 0 deletions tests/test_tenacity.py
Original file line number Diff line number Diff line change
Expand Up @@ -1054,6 +1054,20 @@ def test_retry_if_exception_cause_type(self):
except NameError:
pass

def test_retry_preserves_argument_defaults(self):
def function_with_defaults(a=1):
return a

def function_with_kwdefaults(*, a=1):
return a

retrying = Retrying(wait=tenacity.wait_fixed(0.01), stop=tenacity.stop_after_attempt(3))
wrapped_defaults_function = retrying.wraps(function_with_defaults)
wrapped_kwdefaults_function = retrying.wraps(function_with_kwdefaults)

self.assertEqual(function_with_defaults.__defaults__, wrapped_defaults_function.__defaults__)
self.assertEqual(function_with_kwdefaults.__kwdefaults__, wrapped_kwdefaults_function.__kwdefaults__)

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

0 comments on commit 3100582

Please sign in to comment.