Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding tests #34

Merged
merged 6 commits into from
Apr 14, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions pytypes/type_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -1531,15 +1531,15 @@ def _issubclass(subclass, superclass, bound_Generic=None, bound_typevars=None,

bound_typevars : Optional[Dict[typing.TypeVar, type]]
A dictionary holding values for unbound typevars occurring in ``subclass`` or ``superclass``.
Default: None
Default: {}
Depending on ``bound_typevars_readonly`` pytypes can also bind values to typevars as needed.
This is done by inserting according mappings into this dictionary. This can e.g. be useful to
infer values for ``TypeVar``s or to consistently check a set of ``TypeVar``s across multiple
calls, e.g. when checking all arguments of a function call.
In collision case with ``bound_Generic`` the value from ``bound_Generic`` if preferred.

bound_typevars_readonly : bool
Defines if pytypes is allowed to write into the ``bound_typevars`` dictionary if not ``None``.
Defines if pytypes is allowed to write into the ``bound_typevars`` dictionary.
Default: True
If set to False, pytypes cannot assign values to ``TypeVar``s, but only checks regarding
values already present in ``bound_typevars`` or ``bound_Generic``.
Expand All @@ -1557,6 +1557,8 @@ def _issubclass(subclass, superclass, bound_Generic=None, bound_typevars=None,
a ``_ForwardRef`` is encountered, pytypes automatically creates this dictionary and
continues in recursion-proof manner.
"""
if bound_typevars is None:
bound_typevars = {}
if superclass is Any:
return True
if subclass == superclass:
Expand Down Expand Up @@ -1791,15 +1793,15 @@ def _isinstance(obj, cls, bound_Generic=None, bound_typevars=None,

bound_typevars : Optional[Dict[typing.TypeVar, type]]
A dictionary holding values for unbound typevars occurring in ``cls``.
Default: None
Default: {}
Depending on ``bound_typevars_readonly`` pytypes can also bind values to typevars as needed.
This is done by inserting according mappings into this dictionary. This can e.g. be useful to
infer values for ``TypeVar``s or to consistently check a set of ``TypeVar``s across multiple
calls, e.g. when checking all arguments of a function call.
In collision case with ``bound_Generic`` the value from ``bound_Generic`` if preferred.

bound_typevars_readonly : bool
Defines if pytypes is allowed to write into the ``bound_typevars`` dictionary if not ``None``.
Defines if pytypes is allowed to write into the ``bound_typevars`` dictionary.
Default: True
If set to False, pytypes cannot assign values to ``TypeVar``s, but only checks regarding
values already present in ``bound_typevars`` or ``bound_Generic``.
Expand All @@ -1817,6 +1819,8 @@ def _isinstance(obj, cls, bound_Generic=None, bound_typevars=None,
a ``ForwardRef`` is encountered, pytypes automatically creates this dictionary and
continues in recursion-proof manner.
"""
if bound_typevars is None:
bound_typevars = {}
# Special treatment if cls is Iterable[...]
if is_Generic(cls) and cls.__origin__ is typing.Iterable:
if not is_iterable(obj):
Expand Down
58 changes: 58 additions & 0 deletions tests/test_typechecker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4652,5 +4652,63 @@ def test_inner_class(self):
self.assertRaises(InputTypeError, lambda:
py3.test_inner_class_testf1_err())


class Test_utils(unittest.TestCase):
def test_empty_values(self):
self.assertTrue(pytypes.is_of_type([], typing.Sequence))
self.assertTrue(pytypes.is_of_type([], typing.Sequence[int]))

self.assertTrue(isinstance(set(), typing.Sized))
self.assertTrue(pytypes.is_of_type(set(), typing.Sized))
self.assertTrue(isinstance([], typing.Sized))
self.assertTrue(pytypes.is_of_type([], typing.Sized))

def test_tuple_elipsis(self):
class Foo:
pass

self.assertTrue(pytypes.is_subtype(typing.Tuple[Foo], typing.Tuple[object, ...]))
self.assertTrue(pytypes.is_subtype(typing.Tuple[Foo], typing.Tuple[typing.Any, ...]))

def test_bound_typevars_readonly(self):
T = typing.TypeVar('T', covariant=True)

class L(typing.List[T]):
pass

C = typing.TypeVar('T', bound=L)

self.assertTrue(pytypes.is_subtype(L[float], C))
self.assertTrue(pytypes.is_subtype(L[float], C, bound_typevars={}))
self.assertFalse(pytypes.is_subtype(L[float], C, bound_typevars_readonly=True, bound_typevars={}))
self.assertTrue(pytypes.is_subtype(L[float], C, bound_typevars_readonly=False, bound_typevars={}))

def test_forward_declaration(self):
Container = typing.Union[
typing.List['Data'],
]

Data = typing.Union[
Container,
str, bytes, bool, float, int, dict,
]

with self.assertRaises(pytypes.ForwardRefError):
pytypes.is_subtype(typing.List[float], Container)

pytypes.resolve_fw_decl(Container)

pytypes.is_subtype(typing.List[float], Container)

def test_forward_declaration_infinite_recursion(self):
Data = typing.Union['Container', float]
Container = typing.Union[Data, int]

pytypes.resolve_fw_decl(Data)
pytypes.resolve_fw_decl(Container)

self.assertFalse(pytypes.is_subtype(list, Container))
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think this should raise an exception.

Copy link
Owner

Choose a reason for hiding this comment

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

I'd like to pretend that this succeeds because of recursion proof handling in type_util._issubclass_Union/type_util._issubclass_Union_rec. However I tried to out-comment the recursion-proof part and still get the same result. While this behavior is puzzling I would not investigate further unless we find a particular failure.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

So you are saying you disable recursion checks you added and this test still succeeds?

I copied it from here and at that time it failed for sure: #22 (comment)

Copy link
Owner

Choose a reason for hiding this comment

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

Oh right, the exception is what you actually suggested. Do you find it important to uncover recursive type declarations this way?
I think it would be possible to let type_util._issubclass_Union throw an exception in such a case. I think I'd prefer to have such a behavior optional. Will think about it...

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I do not have strong opinion here. I just see it as an invalid type. So it would simplify debugging. But maybe this type is not invalid and it is true that isinstance should return False, if we would be able to resolve the recursion somehow. So I do not really mind either way. It just be that it will surprise some.



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