Skip to content

Commit

Permalink
specified require and ensure to use generics
Browse files Browse the repository at this point in the history
  • Loading branch information
mristin committed Jan 22, 2019
1 parent 5fec747 commit 6e2f3ee
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 17 deletions.
29 changes: 15 additions & 14 deletions icontract/_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from typing import Callable, Any, Iterable, Optional, Tuple, List, Mapping, MutableMapping, Dict

import icontract._represent
from icontract._globals import CallableT
from icontract._types import Contract, Snapshot
from icontract.errors import ViolationError

Expand All @@ -13,7 +14,7 @@
# pylint: disable=unsubscriptable-object


def _walk_decorator_stack(func: Callable[..., Any]) -> Iterable['Callable[..., Any]']:
def _walk_decorator_stack(func: CallableT) -> Iterable['CallableT']:
"""
Iterate through the stack of decorated functions until the original function.
Expand All @@ -27,9 +28,9 @@ def _walk_decorator_stack(func: Callable[..., Any]) -> Iterable['Callable[..., A
yield func


def find_checker(func: Callable[..., Any]) -> Optional[Callable[..., Any]]:
def find_checker(func: CallableT) -> Optional[CallableT]:
"""Iterate through the decorator stack till we find the contract checker."""
contract_checker = None # type: Optional[Callable[..., Any]]
contract_checker = None # type: Optional[CallableT]
for a_wrapper in _walk_decorator_stack(func):
if hasattr(a_wrapper, "__preconditions__") or hasattr(a_wrapper, "__postconditions__"):
contract_checker = a_wrapper
Expand Down Expand Up @@ -236,7 +237,7 @@ def __repr__(self) -> str:
return "a bunch of OLD values"


def decorate_with_checker(func: Callable[..., Any]) -> Callable[..., Any]:
def decorate_with_checker(func: CallableT) -> CallableT:
"""Decorate the function with a checker that verifies the preconditions and postconditions."""
assert not hasattr(func, "__preconditions__"), \
"Expected func to have no list of preconditions (there should be only a single contract checker per function)."
Expand Down Expand Up @@ -311,7 +312,7 @@ def wrapper(*args, **kwargs):
for contract in postconditions:
_assert_postcondition(contract=contract, resolved_kwargs=resolved_kwargs)

return result
return result # type: ignore

# Copy __doc__ and other properties so that doctests can run
functools.update_wrapper(wrapper=wrapper, wrapped=func)
Expand All @@ -330,7 +331,7 @@ def wrapper(*args, **kwargs):
setattr(wrapper, "__postcondition_snapshots__", [])
setattr(wrapper, "__postconditions__", [])

return wrapper
return wrapper # type: ignore


def _find_self(param_names: List[str], args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> Any:
Expand All @@ -344,7 +345,7 @@ def _find_self(param_names: List[str], args: Tuple[Any, ...], kwargs: Dict[str,
return instance


def _decorate_with_invariants(func: Callable[..., Any], is_init: bool) -> Callable[..., Any]:
def _decorate_with_invariants(func: CallableT, is_init: bool) -> CallableT:
"""
Decorate the function ``func`` of the class ``cls`` with invariant checks.
Expand Down Expand Up @@ -391,7 +392,7 @@ def wrapper(*args, **kwargs):

setattr(wrapper, "__is_invariant_check__", True)

return wrapper
return wrapper # type: ignore


class _DummyClass:
Expand All @@ -403,7 +404,7 @@ class _DummyClass:
_SLOT_WRAPPER_TYPE = type(_DummyClass.__init__) # pylint: disable=invalid-name


def _already_decorated_with_invariants(func: Callable[..., Any]) -> bool:
def _already_decorated_with_invariants(func: CallableT) -> bool:
"""Check if the function has been already decorated with an invariant check by going through its decorator stack."""
already_decorated = False
for a_decorator in _walk_decorator_stack(func=func):
Expand Down Expand Up @@ -467,9 +468,9 @@ def add_invariant_checks(cls: type) -> None:
setattr(cls, name, wrapper)

for name, prop in names_properties:
fget = _decorate_with_invariants(func=prop.fget, is_init=False) if prop.fget else None
fset = _decorate_with_invariants(func=prop.fset, is_init=False) if prop.fset else None
fdel = _decorate_with_invariants(func=prop.fdel, is_init=False) if prop.fdel else None

new_prop = property(fget=fget, fset=fset, fdel=fdel, doc=prop.__doc__)
new_prop = property( # type: ignore
fget=_decorate_with_invariants(func=prop.fget, is_init=False) if prop.fget else None,
fset=_decorate_with_invariants(func=prop.fset, is_init=False) if prop.fset else None,
fdel=_decorate_with_invariants(func=prop.fdel, is_init=False) if prop.fdel else None,
doc=prop.__doc__)
setattr(cls, name, new_prop)
7 changes: 4 additions & 3 deletions icontract/_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import reprlib
from typing import Callable, Optional, Union, Any, List # pylint: disable=unused-import

from icontract._globals import CallableT
from icontract._types import Contract, Snapshot

import icontract._checkers
Expand Down Expand Up @@ -54,7 +55,7 @@ def __init__(self,

self._contract = Contract(condition=condition, description=description, a_repr=a_repr, error=error)

def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]:
def __call__(self, func: CallableT) -> CallableT:
"""
Add the precondition to the list of preconditions of the function ``func``.
Expand Down Expand Up @@ -131,7 +132,7 @@ def __init__(self, capture: Callable[..., Any], name: Optional[str] = None, enab
if enabled:
self._snapshot = Snapshot(capture=capture, name=name)

def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]:
def __call__(self, func: CallableT) -> CallableT:
"""
Add the snapshot to the list of snapshots of the function ``func``.
Expand Down Expand Up @@ -215,7 +216,7 @@ def __init__(self,

self._contract = Contract(condition=condition, description=description, a_repr=a_repr, error=error)

def __call__(self, func: Callable[..., Any]) -> Callable[..., Any]:
def __call__(self, func: CallableT) -> CallableT:
"""
Add the postcondition to the list of postconditions of the function ``func``.
Expand Down
3 changes: 3 additions & 0 deletions icontract/_globals.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

import os
import reprlib
from typing import TypeVar, Callable

# Default representation instance.
#
# The limits are set way higher than reprlib.aRepr since the default reprlib limits are not suitable for
# the production systems.

aRepr = reprlib.Repr() # pylint: disable=invalid-name
aRepr.maxdict = 50
aRepr.maxlist = 50
Expand All @@ -25,3 +27,4 @@
#
# Contracts marked with SLOW are also disabled if the interpreter is run in optimized mode (``-O`` or ``-OO``).
SLOW = __debug__ and os.environ.get("ICONTRACT_SLOW", "") != ""
CallableT = TypeVar('CallableT', bound='Callable')
50 changes: 50 additions & 0 deletions tests/test_mypy_decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# pylint: disable=missing-docstring

import subprocess
import tempfile
import textwrap
import unittest


class TestMypyDecorators(unittest.TestCase):
def test_mypy_me(self):
with tempfile.NamedTemporaryFile(prefix="mypy_fail_case", suffix=".py") as tmp:
tmp.file.write(
textwrap.dedent('''\
"""Implement a fail case for mypy to test that the types are preserved with the decorators."""
import icontract
@icontract.require(lambda x: x > 0)
def f1(x: int):
return x
f1("this is wrong")
@icontract.ensure(lambda result: result > 0)
def f2(x: int):
return x
f2("this is wrong")
@icontract.snapshot(lambda x: x)
def f3(x: int):
return x
f3("this is wrong")
''').encode())

tmp.file.flush()

proc = subprocess.Popen(['mypy', tmp.name], universal_newlines=True, stdout=subprocess.PIPE)
out, err = proc.communicate()

self.assertIsNone(err)
self.assertEqual(
textwrap.dedent('''\
{path}:8: error: Argument 1 to "f1" has incompatible type "str"; expected "int"
{path}:13: error: Argument 1 to "f2" has incompatible type "str"; expected "int"
{path}:18: error: Argument 1 to "f3" has incompatible type "str"; expected "int"
'''.format(path=tmp.name)),
out)


if __name__ == '__main__':
unittest.main()

0 comments on commit 6e2f3ee

Please sign in to comment.