Skip to content

Commit

Permalink
Fixed @TypeChecked not working with built-in wrappers
Browse files Browse the repository at this point in the history
Fixes #89.
  • Loading branch information
agronholm committed Nov 8, 2019
1 parent c4e8646 commit d6d14cf
Show file tree
Hide file tree
Showing 3 changed files with 25 additions and 6 deletions.
3 changes: 2 additions & 1 deletion docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ This library adheres to `Semantic Versioning 2.0 <https://semver.org/#semantic-v

- Fixed import errors when using the import hook and trying to import a module that has both a
module docstring and ``__future__`` imports in it
- Fixed ``AttributeError`` when using @typechecked on a metaclass
- Fixed ``AttributeError`` when using ``@typechecked`` on a metaclass
- Fixed ``@typechecked`` compatibility with built-in function wrappers

**2.6.0** (2019-11-06)

Expand Down
17 changes: 16 additions & 1 deletion tests/test_typeguard.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import sys
import warnings
from concurrent.futures import ThreadPoolExecutor
from functools import wraps, partial
from functools import wraps, partial, lru_cache
from io import StringIO, BytesIO
from typing import (
Any, Callable, Dict, List, Set, Tuple, Union, TypeVar, Sequence, NamedTuple, Iterable,
Expand Down Expand Up @@ -947,6 +947,21 @@ def genfunc() -> Generator[int, str, str]:

exc.match('type of return value must be str; got int instead')

def test_builtin_decorator(self):
@typechecked
@lru_cache()
def func(x: int) -> None:
pass

func(3)
func(3)
pytest.raises(TypeError, func, 'foo').\
match('type of argument "x" must be int; got str instead')

# Make sure that @lru_cache is still being used
cache_info = func.__wrapped__.cache_info()
assert cache_info.hits == 1


class TestTypeChecker:
@pytest.fixture
Expand Down
11 changes: 7 additions & 4 deletions typeguard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,12 +724,15 @@ class with this decorator.

return func

# Find either the first Python wrapper or the actual function
python_func = inspect.unwrap(func, stop=lambda f: hasattr(f, '__code__'))

if not getattr(func, '__annotations__', None):
warn('no type annotations present -- not typechecking {}'.format(function_name(func)))
return func

def wrapper(*args, **kwargs):
memo = _CallMemo(func, args=args, kwargs=kwargs)
memo = _CallMemo(python_func, args=args, kwargs=kwargs)
check_argument_types(memo)
retval = func(*args, **kwargs)
check_return_type(retval, memo)
Expand All @@ -746,17 +749,17 @@ def wrapper(*args, **kwargs):
return retval

async def async_wrapper(*args, **kwargs):
memo = _CallMemo(func, args=args, kwargs=kwargs)
memo = _CallMemo(python_func, args=args, kwargs=kwargs)
check_argument_types(memo)
retval = await func(*args, **kwargs)
check_return_type(retval, memo)
return retval

if inspect.iscoroutinefunction(func):
if func.__code__ is not async_wrapper.__code__:
if python_func.__code__ is not async_wrapper.__code__:
return wraps(func)(async_wrapper)
else:
if func.__code__ is not wrapper.__code__:
if python_func.__code__ is not wrapper.__code__:
return wraps(func)(wrapper)

# the target callable was already wrapped
Expand Down

0 comments on commit d6d14cf

Please sign in to comment.