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 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
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 @@ -1535,15 +1535,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 @@ -1561,6 +1561,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 @@ -1795,15 +1797,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 @@ -1821,6 +1823,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
65 changes: 65 additions & 0 deletions tests/test_typechecker.py
Original file line number Diff line number Diff line change
Expand Up @@ -4668,6 +4668,71 @@ class Foo(typing.Generic[T]):
def test_frozenset(self):
self.assertTrue(pytypes.is_of_type(frozenset({1, 2, 'a', None, 'b'}), typing.AbstractSet[typing.Union[str, int, None]]))

# See: https://github.com/Stewori/pytypes/issues/32
# See: https://github.com/Stewori/pytypes/issues/33
def test_empty_values(self):
self.assertTrue(pytypes.is_of_type([], typing.Sequence))
self.assertTrue(pytypes.is_of_type([], typing.Sequence[int]))

for interface in (typing.Iterable, typing.Sized, typing.Container):
self.assertTrue(isinstance(set(), interface), interface)
self.assertTrue(pytypes.is_of_type(set(), interface), interface)
self.assertTrue(isinstance([], interface), interface)
self.assertTrue(pytypes.is_of_type([], interface), interface)

# See: https://github.com/Stewori/pytypes/issues/21
def test_tuple_ellipsis(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, ...]))

# See: https://github.com/Stewori/pytypes/issues/24
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={}))

# See: https://github.com/Stewori/pytypes/issues/22
def test_forward_declaration(self):
Wrapper = typing.Union[
typing.Sequence['Data'],
]

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

with self.assertRaises(pytypes.ForwardRefError):
pytypes.is_subtype(typing.Sequence[float], Wrapper)

pytypes.resolve_fw_decl(Wrapper)

self.assertTrue(pytypes.is_subtype(typing.Sequence[float], Wrapper))
Copy link
Owner

Choose a reason for hiding this comment

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

self.assertTrue(pytypes.is_subtype(typing.List[float], Wrapper)) should work as well, i.e. changing List to Sequence was not necessary here, only in declaration of Wrapper.

self.assertTrue(pytypes.is_subtype(int, Data))
self.assertTrue(pytypes.is_subtype(float, Data))
self.assertFalse(pytypes.is_subtype(Data, Wrapper))
self.assertFalse(pytypes.is_subtype(Wrapper, Data))
Copy link
Owner

Choose a reason for hiding this comment

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

self.assertFalse(pytypes.is_subtype(Wrapper, Data)) is failing now. Unlike the line above, this is actually true. As participant of the Data-Union, Wrapper is indeed a subtype of it. As discussed earlier the converse is not true.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed. :-) I think I have no clue about typing anymore. ;-)

Copy link
Owner

Choose a reason for hiding this comment

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

Sorry to hear that. Hope it doesn't stop you from helping to improve pytypes ;)
Feel free to ask more detals if it helps. Here I think it boils down to union semantics, so let me point out the main rules again:
Union[x, y] is subtype of z iff x is subtype of z and y is subtype of z.
z is subtype of Union[x, y] iff z is subtype of x or z is subtype of y.
This directly corresponds to union operation from set theory if x, y, z are sets and subtype means subset. There is also the intersection correspondence in type theory, but PEP 484 does not provide an intersection type (was once discussed in an issue of typing, but then drpped because rare use cases did not outweight the difficulties in implementing it for static type checking/mypy)


# See: https://github.com/Stewori/pytypes/issues/22
def test_forward_declaration_infinite_recursion(self):
Data = typing.Union['Wrapper', float]
Wrapper = typing.Union[Data, int]

pytypes.resolve_fw_decl(Data)

self.assertFalse(pytypes.is_subtype(list, Wrapper))


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