Skip to content

Commit

Permalink
#3 default values for params should always be acceptable, even if the…
Browse files Browse the repository at this point in the history
…y contradict the param's type
  • Loading branch information
st-pasha committed Jan 31, 2017
1 parent ccdae8a commit e0d010f
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 31 deletions.
3 changes: 3 additions & 0 deletions .coveragerc
Expand Up @@ -12,6 +12,9 @@ exclude_lines =
if False:
if __name__ == .__main__.:

# Mostly debugging code:
def __repr__\(self\):

# the number of digits after the decimal point to display for reported
# coverage percentages
precision = 2
34 changes: 34 additions & 0 deletions tests/test_kwonly.py
Expand Up @@ -3,6 +3,10 @@
import pytest
from tests import typed, py3only, TypeError

foo = 1
bar = 2
baz = 3


def test_kwonly1():
@typed(_kwonly=2)
Expand Down Expand Up @@ -87,6 +91,21 @@ def ooz(a, b=2, x=None):

@py3only
def test_py3_signatures():
exec("@typed(x=int, y=int)\n"
"def bar(x, *, y):\n"
" return x + y\n", locals(), globals())

assert bar(1, y=2) == 3
assert bar(x=5, y=7) == 12

exec("@typed(x=int, y=int)\n"
"def baz(x, *, y=5):\n"
" return x + y\n", locals(), globals())

assert baz(5) == 10
assert baz(x=1) == 6
assert baz(x=1, y=2) == 3

with pytest.raises(RuntimeError) as e:
exec("@typed(_kwonly=1)\n"
"def foo(x, *, y):\n"
Expand All @@ -95,7 +114,14 @@ def test_py3_signatures():
"argument in Python3"



def test_bad_declarations():
with pytest.raises(RuntimeError) as e:
@typed(_kwonly=None)
def foo0():
pass
assert str(e.value) == "_kwonly parameter should be an integer"

with pytest.raises(RuntimeError) as e:
@typed(_kwonly=1)
def foo2():
Expand All @@ -113,3 +139,11 @@ def foo3(*args):
def foo4(*args, **varargs):
pass
assert str(e.value) == "Too many keyword-only parameters requested"

with pytest.raises(RuntimeError) as e:
class A(object):
@typed(_kwonly=1)
def __init__(self):
pass
assert str(e.value) == "POSITIONAL_ONLY parameter self cannot be made " \
"KEYWORD_ONLY"
31 changes: 30 additions & 1 deletion tests/test_signature.py
Expand Up @@ -117,6 +117,7 @@ def foo(*args):
"integer got string"



def test_func_varkws():
@typed(kws=float)
def foo(**kws):
Expand All @@ -136,6 +137,7 @@ def foo(**kws):
assert str(e.value) == "`foo()` accepts only keyword arguments"



def test_return_value():
@typed(_return=float)
def foo():
Expand All @@ -156,10 +158,19 @@ def bar(x):


def test_bad_declaration():
with pytest.raises(RuntimeError):
with pytest.raises(RuntimeError) as e:
@typed(z=int)
def foo1():
pass
assert str(e.value) == "Invalid function argument(s): z"

with pytest.raises(RuntimeError) as e:
class A(object):
@typed(self=int)
def __init__(self):
pass
assert str(e.value) == "`self` parameter must not be typed"



@py3only
Expand All @@ -170,3 +181,21 @@ def test_function_with_signature():

assert foo() # noqa
assert foo(1) # noqa



def test_defaults():
@typed(x=int)
def foo(x=None):
return True

assert foo()
assert foo(5)
assert foo(None)
assert foo(x=10)
assert foo(x=None)

with pytest.raises(TypeError) as e:
foo(x="")
assert str(e.value) == "Incorrect type for argument `x`: expected " \
"integer got string"
66 changes: 36 additions & 30 deletions typesentry/signature.py
Expand Up @@ -71,6 +71,11 @@ def _fill_from_inspection_spec(self, types):
self._min_positional_args = len(fspec.args)
for arg in fspec.args:
p = Parameter(arg)
if arg == "self" and len(self.params) == 0:
self._num_self_args = 1
p = Parameter("self", kind="POSITIONAL_ONLY")
if "self" in types:
raise RuntimeError("`self` parameter must not be typed")
if arg in types:
p.type = types.pop(arg)
self.params.append(p)
Expand All @@ -96,9 +101,11 @@ def _fill_from_inspection_spec(self, types):
self.params.append(p)

if getattr(fspec, "kwonlydefaults", None):
for i in range(1, len(fspec.kwonlydefaults) + 1):
self.params[-i].default = fspec.kwonlydefaults[-i]
self._required_kwonly_args.remove(self.params[-i].name)
fkw = fspec.kwonlydefaults
for i in range(1, len(fkw) + 1):
p = self.params[-i]
p.default = fkw[p.name]
self._required_kwonly_args.remove(p.name)

if getattr(fspec, "varkw", None) or getattr(fspec, "keywords", None):
vkw = getattr(fspec, "varkw", None) or getattr(fspec, "keywords")
Expand Down Expand Up @@ -142,14 +149,11 @@ def _fill_from_inspection_spec(self, types):
self._max_positional_args)

if types:
raise RuntimeError("Invalid function argument(s): %r" %
list(types.keys()))
raise RuntimeError("Invalid function argument(s): %s" %
", ".join(types.keys()))

self._iargs = {param.name: i for i, param in enumerate(self.params)}

if self.params and self.params[0].name == "self":
self._num_self_args = 1



def _make_retval_checker(self):
Expand Down Expand Up @@ -215,18 +219,11 @@ def _check_positional_arg(self, index, value):
if index < self._max_positional_args:
param = self.params[index]
argname = param.name
checker = param.checker
else:
assert self._ivararg is not None
param = self.params[self._ivararg]
argname = "*" + param.name
checker = param.checker
if checker and not checker.check(value):
tval = checker_for_type(type(value)).name()
raise self._tc.TypeError(
"Incorrect type for argument `%s`: expected %s got %s" %
(argname, checker.name(), tval)
)
self._check_arg(param, argname, value)


def _check_keyword_arg(self, name, value):
Expand All @@ -237,15 +234,24 @@ def _check_keyword_arg(self, name, value):
s = "%s got an unexpected keyword argument `%s`" % \
(self.name_bt, name)
raise self._tc.TypeError(s)

checker = self.params[index].checker
if checker:
if not checker.check(value):
tval = checker_for_type(type(value)).name()
raise self._tc.TypeError(
"Incorrect type for argument `%s`: expected %s got %s" %
(name, checker.name(), tval)
)
self._check_arg(self.params[index], name, value)


def _check_arg(self, param, name, value):
checker = param.checker
if not checker:
return
if checker.check(value):
return
if param.has_default:
dflt = param.default
if value is dflt or value == dflt:
return
tval = checker_for_type(type(value)).name()
raise self._tc.TypeError(
"Incorrect type for argument `%s`: expected %s got %s" %
(name, checker.name(), tval)
)


@property
Expand Down Expand Up @@ -326,7 +332,7 @@ def __init__(self, name, kind="POSITIONAL_OR_KEYWORD"):


@property
def type(self):
def type(self): # pragma: no cover
return self._type

@type.setter
Expand All @@ -338,14 +344,14 @@ def type(self, t):
def checker(self):
return self._checker

@property
def has_default(self):
return self._has_default

@property
def is_required(self):
return not self._has_default

@property
def has_default(self):
return self._has_default

@property
def default(self):
return self._default
Expand Down

0 comments on commit e0d010f

Please sign in to comment.