bpo-46571: improve typing.no_type_check to skip foreign objects (GH-31042)

There are several changes:
1. We now don't explicitly check for any base / sub types, because new name check covers it
2. I've also checked that `no_type_check` do not modify foreign functions. It was the same as with `type`s
3. I've also covered `except TypeError` in `no_type_check` with a simple test case, it was not covered at all
4. I also felt like adding `lambda` test is a good idea: because `lambda` is a bit of both in class bodies: a function and an assignment

<!-- issue-number: [bpo-46571](https://bugs.python.org/issue46571) -->
https://bugs.python.org/issue46571
<!-- /issue-number -->
This commit is contained in:
Nikita Sobolev 2022-02-19 04:53:29 +03:00 committed by GitHub
parent f80a97b492
commit 395029b0bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 132 additions and 7 deletions

View file

@ -2744,6 +2744,18 @@ class CastTests(BaseTestCase):
cast('hello', 42)
# We need this to make sure that `@no_type_check` respects `__module__` attr:
from test import ann_module8
@no_type_check
class NoTypeCheck_Outer:
Inner = ann_module8.NoTypeCheck_Outer.Inner
@no_type_check
class NoTypeCheck_WithFunction:
NoTypeCheck_function = ann_module8.NoTypeCheck_function
class ForwardRefTests(BaseTestCase):
def test_basics(self):
@ -3058,9 +3070,98 @@ class ForwardRefTests(BaseTestCase):
@no_type_check
class D(C):
c = C
# verify that @no_type_check never affects bases
self.assertEqual(get_type_hints(C.meth), {'x': int})
# and never child classes:
class Child(D):
def foo(self, x: int): ...
self.assertEqual(get_type_hints(Child.foo), {'x': int})
def test_no_type_check_nested_types(self):
# See https://bugs.python.org/issue46571
class Other:
o: int
class B: # Has the same `__name__`` as `A.B` and different `__qualname__`
o: int
@no_type_check
class A:
a: int
class B:
b: int
class C:
c: int
class D:
d: int
Other = Other
for klass in [A, A.B, A.B.C, A.D]:
with self.subTest(klass=klass):
self.assertTrue(klass.__no_type_check__)
self.assertEqual(get_type_hints(klass), {})
for not_modified in [Other, B]:
with self.subTest(not_modified=not_modified):
with self.assertRaises(AttributeError):
not_modified.__no_type_check__
self.assertNotEqual(get_type_hints(not_modified), {})
def test_no_type_check_class_and_static_methods(self):
@no_type_check
class Some:
@staticmethod
def st(x: int) -> int: ...
@classmethod
def cl(cls, y: int) -> int: ...
self.assertTrue(Some.st.__no_type_check__)
self.assertEqual(get_type_hints(Some.st), {})
self.assertTrue(Some.cl.__no_type_check__)
self.assertEqual(get_type_hints(Some.cl), {})
def test_no_type_check_other_module(self):
self.assertTrue(NoTypeCheck_Outer.__no_type_check__)
with self.assertRaises(AttributeError):
ann_module8.NoTypeCheck_Outer.__no_type_check__
with self.assertRaises(AttributeError):
ann_module8.NoTypeCheck_Outer.Inner.__no_type_check__
self.assertTrue(NoTypeCheck_WithFunction.__no_type_check__)
with self.assertRaises(AttributeError):
ann_module8.NoTypeCheck_function.__no_type_check__
def test_no_type_check_foreign_functions(self):
# We should not modify this function:
def some(*args: int) -> int:
...
@no_type_check
class A:
some_alias = some
some_class = classmethod(some)
some_static = staticmethod(some)
with self.assertRaises(AttributeError):
some.__no_type_check__
self.assertEqual(get_type_hints(some), {'args': int, 'return': int})
def test_no_type_check_lambda(self):
@no_type_check
class A:
# Corner case: `lambda` is both an assignment and a function:
bar: Callable[[int], int] = lambda arg: arg
self.assertTrue(A.bar.__no_type_check__)
self.assertEqual(get_type_hints(A.bar), {})
def test_no_type_check_TypeError(self):
# This simply should not fail with
# `TypeError: can't set attributes of built-in/extension type 'dict'`
no_type_check(dict)
def test_no_type_check_forward_ref_as_string(self):
class C:
foo: typing.ClassVar[int] = 7