-
Notifications
You must be signed in to change notification settings - Fork 259
A real fix for issue #250 (failure with mock) #295
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
Changes from 1 commit
b07b8ea
eb7801b
be5d7ed
63482fa
307bf64
f4932bb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -630,13 +630,55 @@ def test_orig_bases(self): | |
class C(typing.Dict[str, T]): ... | ||
self.assertEqual(C.__orig_bases__, (typing.Dict[str, T],)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I know there are no docs for There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is quite difficult to write a reasonable runtime type check functions. I just added three naive functions just to illustrate how to use the dunder attributes for typical type checking tasks. In process of doing this I realized that it is difficult to perform runtime type checks since type erasure happens not only at subclassing, but also at generic class instantiation. In the new commit I added |
||
|
||
def test_naive_runtime_checks(self): | ||
def naive_dict_check(obj, tp): | ||
# Check if a dictionary conforms to Dict type | ||
if len(tp.__parameters__) > 0: | ||
return NotImplemented | ||
if tp.__args__: | ||
KT, VT = tp.__args__ | ||
return all(isinstance(k, KT) and isinstance(v, VT) | ||
for k, v in obj.items()) | ||
self.assertTrue(naive_dict_check({'x': 1}, typing.Dict[str, int])) | ||
self.assertFalse(naive_dict_check({1: 'x'}, typing.Dict[str, int])) | ||
self.assertIs(naive_dict_check({1: 'x'}, typing.Dict[str, T]), NotImplemented) | ||
|
||
def naive_generic_check(obj, tp): | ||
# Check if an instance conforms to the generic class | ||
if not hasattr(obj, '__orig_class__'): | ||
return NotImplemented | ||
return obj.__orig_class__ == tp | ||
class Node(Generic[T]): ... | ||
self.assertTrue(naive_generic_check(Node[int](), Node[int])) | ||
self.assertFalse(naive_generic_check(Node[str](), Node[int])) | ||
self.assertFalse(naive_generic_check(Node[str](), List)) | ||
self.assertIs(naive_generic_check([1,2,3], Node[int]), NotImplemented) | ||
|
||
def naive_list_base_check(obj, tp): | ||
# Check if list conforms to a List subclass | ||
return all(isinstance(x, tp.__orig_bases__[0].__args__[0]) | ||
for x in obj) | ||
class C(List[int]): ... | ||
self.assertTrue(naive_list_base_check([1, 2, 3], C)) | ||
self.assertFalse(naive_list_base_check(['a', 'b'], C)) | ||
|
||
def test_multi_subscr_base(self): | ||
T = TypeVar('T') | ||
U = TypeVar('U') | ||
V = TypeVar('V') | ||
# these should just work | ||
class C(List[T][U][V]): ... | ||
class D(C, List[T][U][V]): ... | ||
self.assertEqual(C.__parameters__, (V,)) | ||
self.assertEqual(D.__parameters__, (V,)) | ||
self.assertEqual(C[int].__parameters__, ()) | ||
self.assertEqual(D[int].__parameters__, ()) | ||
self.assertEqual(C[int].__args__, (int,)) | ||
self.assertEqual(D[int].__args__, (int,)) | ||
self.assertEqual(C.__bases__, (List,)) | ||
self.assertEqual(D.__bases__, (C, List)) | ||
self.assertEqual(C.__orig_bases__, (List[T][U][V],)) | ||
self.assertEqual(D.__orig_bases__, (C, List[T][U][V])) | ||
|
||
|
||
def test_pickle(self): | ||
global C # pickle wants to reference the class by name | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -938,7 +938,7 @@ class GenericMeta(TypingMeta, abc.ABCMeta): | |
"""Metaclass for generic types.""" | ||
|
||
def __new__(cls, name, bases, namespace, | ||
tvars=None, args=None, origin=None, extra=None): | ||
tvars=None, args=None, origin=None, extra=None, orig_bases=None): | ||
if tvars is not None: | ||
# Called from __getitem__() below. | ||
assert origin is not None | ||
|
@@ -979,7 +979,7 @@ def __new__(cls, name, bases, namespace, | |
", ".join(str(g) for g in gvars))) | ||
tvars = gvars | ||
|
||
orig_bases = bases | ||
initial_bases = bases | ||
if extra is not None and type(extra) is abc.ABCMeta and extra not in bases: | ||
bases = (extra,) + bases | ||
bases = tuple(_gorg(b) if isinstance(b, GenericMeta) else b for b in bases) | ||
|
@@ -995,8 +995,9 @@ def __new__(cls, name, bases, namespace, | |
self.__extra__ = extra | ||
# Speed hack (https://github.com/python/typing/issues/196). | ||
self.__next_in_mro__ = _next_in_mro(self) | ||
if origin is None: | ||
self.__orig_bases__ = orig_bases | ||
# Preserve base classes on subclassing (__bases__ are type erased now). | ||
if orig_bases is None: | ||
self.__orig_bases__ = initial_bases | ||
|
||
# This allows unparameterized generic collections to be used | ||
# with issubclass() and isinstance() in the same way as their | ||
|
@@ -1084,7 +1085,8 @@ def __getitem__(self, params): | |
tvars=tvars, | ||
args=args, | ||
origin=self, | ||
extra=self.__extra__) | ||
extra=self.__extra__, | ||
orig_bases=self.__orig_bases__) | ||
|
||
def __instancecheck__(self, instance): | ||
# Since we extend ABC.__subclasscheck__ and | ||
|
@@ -1128,6 +1130,8 @@ def __new__(cls, *args, **kwds): | |
else: | ||
origin = _gorg(cls) | ||
obj = cls.__next_in_mro__.__new__(origin) | ||
if '__dict__' in cls.__dict__: | ||
obj.__orig_class__ = cls | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'd just catch AttributeError here -- the "dict in dict" idiom feels obscure. |
||
obj.__init__(*args, **kwds) | ||
return obj | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NotImplemented is not a great value to return here -- it's really only supposed to be used by binary operators (e.g.
__add__
) to indicate that the reverse variant should be tried (i.e.__radd__
). Maybe raising NotImplementedError would be better?While this is just a test, it will be used as an example and copied, and raising makes it clearer that that's an unimplemented feature of the naive check.