diff options
Diffstat (limited to 'Lib/test/test_inspect.py')
-rw-r--r-- | Lib/test/test_inspect.py | 513 |
1 files changed, 456 insertions, 57 deletions
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 1a124b5db2..955b2adcb2 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -1,3 +1,4 @@ +import builtins import collections import datetime import functools @@ -8,6 +9,7 @@ import linecache import os from os.path import normcase import _pickle +import pickle import re import shutil import sys @@ -16,6 +18,7 @@ import textwrap import unicodedata import unittest import unittest.mock +import warnings try: from concurrent.futures import ThreadPoolExecutor @@ -23,8 +26,8 @@ except ImportError: ThreadPoolExecutor = None from test.support import run_unittest, TESTFN, DirsOnSysPath, cpython_only -from test.support import MISSING_C_DOCSTRINGS -from test.script_helper import assert_python_ok, assert_python_failure +from test.support import MISSING_C_DOCSTRINGS, cpython_only +from test.support.script_helper import assert_python_ok, assert_python_failure from test import inspect_fodder as mod from test import inspect_fodder2 as mod2 @@ -60,14 +63,16 @@ class IsTestBase(unittest.TestCase): predicates = set([inspect.isbuiltin, inspect.isclass, inspect.iscode, inspect.isframe, inspect.isfunction, inspect.ismethod, inspect.ismodule, inspect.istraceback, - inspect.isgenerator, inspect.isgeneratorfunction]) + inspect.isgenerator, inspect.isgeneratorfunction, + inspect.iscoroutine, inspect.iscoroutinefunction]) def istest(self, predicate, exp): obj = eval(exp) self.assertTrue(predicate(obj), '%s(%s)' % (predicate.__name__, exp)) for other in self.predicates - set([predicate]): - if predicate == inspect.isgeneratorfunction and\ + if (predicate == inspect.isgeneratorfunction or \ + predicate == inspect.iscoroutinefunction) and \ other == inspect.isfunction: continue self.assertFalse(other(obj), 'not %s(%s)' % (other.__name__, exp)) @@ -76,19 +81,19 @@ def generator_function_example(self): for i in range(2): yield i +async def coroutine_function_example(self): + return 'spam' + +@types.coroutine +def gen_coroutine_function_example(self): + yield + return 'spam' + class EqualsToAll: def __eq__(self, other): return True class TestPredicates(IsTestBase): - def test_sixteen(self): - count = len([x for x in dir(inspect) if x.startswith('is')]) - # This test is here for remember you to update Doc/library/inspect.rst - # which claims there are 16 such functions - expected = 16 - err_msg = "There are %d (not %d) is* functions" % (count, expected) - self.assertEqual(count, expected, err_msg) - def test_excluding_predicates(self): global tb @@ -116,11 +121,62 @@ class TestPredicates(IsTestBase): self.istest(inspect.isdatadescriptor, 'collections.defaultdict.default_factory') self.istest(inspect.isgenerator, '(x for x in range(2))') self.istest(inspect.isgeneratorfunction, 'generator_function_example') + + with warnings.catch_warnings(): + warnings.simplefilter("ignore") + self.istest(inspect.iscoroutine, 'coroutine_function_example(1)') + self.istest(inspect.iscoroutinefunction, 'coroutine_function_example') + if hasattr(types, 'MemberDescriptorType'): self.istest(inspect.ismemberdescriptor, 'datetime.timedelta.days') else: self.assertFalse(inspect.ismemberdescriptor(datetime.timedelta.days)) + def test_iscoroutine(self): + gen_coro = gen_coroutine_function_example(1) + coro = coroutine_function_example(1) + + self.assertFalse( + inspect.iscoroutinefunction(gen_coroutine_function_example)) + self.assertFalse(inspect.iscoroutine(gen_coro)) + + self.assertTrue( + inspect.isgeneratorfunction(gen_coroutine_function_example)) + self.assertTrue(inspect.isgenerator(gen_coro)) + + self.assertTrue( + inspect.iscoroutinefunction(coroutine_function_example)) + self.assertTrue(inspect.iscoroutine(coro)) + + self.assertFalse( + inspect.isgeneratorfunction(coroutine_function_example)) + self.assertFalse(inspect.isgenerator(coro)) + + coro.close(); gen_coro.close() # silence warnings + + def test_isawaitable(self): + def gen(): yield + self.assertFalse(inspect.isawaitable(gen())) + + coro = coroutine_function_example(1) + gen_coro = gen_coroutine_function_example(1) + + self.assertTrue(inspect.isawaitable(coro)) + self.assertTrue(inspect.isawaitable(gen_coro)) + + class Future: + def __await__(): + pass + self.assertTrue(inspect.isawaitable(Future())) + self.assertFalse(inspect.isawaitable(Future)) + + class NotFuture: pass + not_fut = NotFuture() + not_fut.__await__ = lambda: None + self.assertFalse(inspect.isawaitable(not_fut)) + + coro.close(); gen_coro.close() # silence warnings + def test_isroutine(self): self.assertTrue(inspect.isroutine(mod.spam)) self.assertTrue(inspect.isroutine([].count)) @@ -186,6 +242,14 @@ class TestInterpreterStack(IsTestBase): (modfile, 43, 'argue', [' spam(a, b, c)\n'], 0)) self.assertEqual(revise(*mod.st[3][1:]), (modfile, 39, 'abuse', [' self.argue(a, b, c)\n'], 0)) + # Test named tuple fields + record = mod.st[0] + self.assertIs(record.frame, mod.fr) + self.assertEqual(record.lineno, 16) + self.assertEqual(record.filename, mod.__file__) + self.assertEqual(record.function, 'eggs') + self.assertIn('inspect.stack()', record.code_context[0]) + self.assertEqual(record.index, 0) def test_trace(self): self.assertEqual(len(git.tr), 3) @@ -217,9 +281,7 @@ class GetSourceBase(unittest.TestCase): # Subclasses must override. fodderModule = None - def __init__(self, *args, **kwargs): - unittest.TestCase.__init__(self, *args, **kwargs) - + def setUp(self): with open(inspect.getsourcefile(self.fodderModule)) as fp: self.source = fp.read() @@ -274,6 +336,7 @@ class TestRetrievingSourceCode(GetSourceBase): def test_getfunctions(self): functions = inspect.getmembers(mod, inspect.isfunction) self.assertEqual(functions, [('eggs', mod.eggs), + ('lobbest', mod.lobbest), ('spam', mod.spam)]) @unittest.skipIf(sys.flags.optimize >= 2, @@ -285,6 +348,27 @@ class TestRetrievingSourceCode(GetSourceBase): self.assertEqual(inspect.getdoc(git.abuse), 'Another\n\ndocstring\n\ncontaining\n\ntabs') + @unittest.skipIf(sys.flags.optimize >= 2, + "Docstrings are omitted with -O2 and above") + def test_getdoc_inherited(self): + self.assertEqual(inspect.getdoc(mod.FesteringGob), + 'A longer,\n\nindented\n\ndocstring.') + self.assertEqual(inspect.getdoc(mod.FesteringGob.abuse), + 'Another\n\ndocstring\n\ncontaining\n\ntabs') + self.assertEqual(inspect.getdoc(mod.FesteringGob().abuse), + 'Another\n\ndocstring\n\ncontaining\n\ntabs') + self.assertEqual(inspect.getdoc(mod.FesteringGob.contradiction), + 'The automatic gainsaying.') + + @unittest.skipIf(MISSING_C_DOCSTRINGS, "test requires docstrings") + def test_finddoc(self): + finddoc = inspect._finddoc + self.assertEqual(finddoc(int), int.__doc__) + self.assertEqual(finddoc(int.to_bytes), int.to_bytes.__doc__) + self.assertEqual(finddoc(int().to_bytes), int.to_bytes.__doc__) + self.assertEqual(finddoc(int.from_bytes), int.from_bytes.__doc__) + self.assertEqual(finddoc(int.real), int.real.__doc__) + def test_cleandoc(self): self.assertEqual(inspect.cleandoc('An\n indented\n docstring.'), 'An\nindented\ndocstring.') @@ -309,7 +393,8 @@ class TestRetrievingSourceCode(GetSourceBase): def test_getsource(self): self.assertSourceEqual(git.abuse, 29, 39) - self.assertSourceEqual(mod.StupidGit, 21, 46) + self.assertSourceEqual(mod.StupidGit, 21, 50) + self.assertSourceEqual(mod.lobbest, 70, 71) def test_getsourcefile(self): self.assertEqual(normcase(inspect.getsourcefile(mod.spam)), modfile) @@ -364,6 +449,9 @@ class TestRetrievingSourceCode(GetSourceBase): finally: linecache.getlines = getlines + def test_getsource_on_code_object(self): + self.assertSourceEqual(mod.eggs.__code__, 12, 18) + class TestDecorators(GetSourceBase): fodderModule = mod2 @@ -373,6 +461,12 @@ class TestDecorators(GetSourceBase): def test_replacing_decorator(self): self.assertSourceEqual(mod2.gone, 9, 10) + def test_getsource_unwrap(self): + self.assertSourceEqual(mod2.real, 130, 132) + + def test_decorator_with_lambda(self): + self.assertSourceEqual(mod2.func114, 113, 115) + class TestOneliners(GetSourceBase): fodderModule = mod2 def test_oneline_lambda(self): @@ -466,8 +560,15 @@ class TestBuggyCases(GetSourceBase): self.assertRaises(IOError, inspect.findsource, co) self.assertRaises(IOError, inspect.getsource, co) + def test_getsource_on_method(self): + self.assertSourceEqual(mod2.ClassWithMethod.method, 118, 119) + + def test_nested_func(self): + self.assertSourceEqual(mod2.cls135.func136, 136, 139) + + class TestNoEOL(GetSourceBase): - def __init__(self, *args, **kwargs): + def setUp(self): self.tempdir = TESTFN + '_dir' os.mkdir(self.tempdir) with open(os.path.join(self.tempdir, @@ -476,7 +577,7 @@ class TestNoEOL(GetSourceBase): with DirsOnSysPath(self.tempdir): import inspect_fodder3 as mod3 self.fodderModule = mod3 - GetSourceBase.__init__(self, *args, **kwargs) + super().setUp() def tearDown(self): shutil.rmtree(self.tempdir) @@ -529,7 +630,8 @@ class TestClassesAndFunctions(unittest.TestCase): def assertArgSpecEquals(self, routine, args_e, varargs_e=None, varkw_e=None, defaults_e=None, formatted=None): - args, varargs, varkw, defaults = inspect.getargspec(routine) + with self.assertWarns(DeprecationWarning): + args, varargs, varkw, defaults = inspect.getargspec(routine) self.assertEqual(args, args_e) self.assertEqual(varargs, varargs_e) self.assertEqual(varkw, varkw_e) @@ -1634,10 +1736,86 @@ class TestGetGeneratorState(unittest.TestCase): self.assertRaises(TypeError, inspect.getgeneratorlocals, (2,3)) +class TestGetCoroutineState(unittest.TestCase): + + def setUp(self): + @types.coroutine + def number_coroutine(): + for number in range(5): + yield number + async def coroutine(): + await number_coroutine() + self.coroutine = coroutine() + + def tearDown(self): + self.coroutine.close() + + def _coroutinestate(self): + return inspect.getcoroutinestate(self.coroutine) + + def test_created(self): + self.assertEqual(self._coroutinestate(), inspect.CORO_CREATED) + + def test_suspended(self): + self.coroutine.send(None) + self.assertEqual(self._coroutinestate(), inspect.CORO_SUSPENDED) + + def test_closed_after_exhaustion(self): + while True: + try: + self.coroutine.send(None) + except StopIteration: + break + + self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + + def test_closed_after_immediate_exception(self): + with self.assertRaises(RuntimeError): + self.coroutine.throw(RuntimeError) + self.assertEqual(self._coroutinestate(), inspect.CORO_CLOSED) + + def test_easy_debugging(self): + # repr() and str() of a coroutine state should contain the state name + names = 'CORO_CREATED CORO_RUNNING CORO_SUSPENDED CORO_CLOSED'.split() + for name in names: + state = getattr(inspect, name) + self.assertIn(name, repr(state)) + self.assertIn(name, str(state)) + + def test_getcoroutinelocals(self): + @types.coroutine + def gencoro(): + yield + + gencoro = gencoro() + async def func(a=None): + b = 'spam' + await gencoro + + coro = func() + self.assertEqual(inspect.getcoroutinelocals(coro), + {'a': None, 'gencoro': gencoro}) + coro.send(None) + self.assertEqual(inspect.getcoroutinelocals(coro), + {'a': None, 'gencoro': gencoro, 'b': 'spam'}) + + +class MySignature(inspect.Signature): + # Top-level to make it picklable; + # used in test_signature_object_pickle + pass + +class MyParameter(inspect.Parameter): + # Top-level to make it picklable; + # used in test_signature_object_pickle + pass + + + class TestSignatureObject(unittest.TestCase): @staticmethod - def signature(func): - sig = inspect.signature(func) + def signature(func, **kw): + sig = inspect.signature(func, **kw) return (tuple((param.name, (... if param.default is param.empty else param.default), (... if param.annotation is param.empty @@ -1691,6 +1869,37 @@ class TestSignatureObject(unittest.TestCase): with self.assertRaisesRegex(ValueError, 'follows default argument'): S((pkd, pk)) + self.assertTrue(repr(sig).startswith('<Signature')) + self.assertTrue('(po, pk' in repr(sig)) + + def test_signature_object_pickle(self): + def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass + foo_partial = functools.partial(foo, a=1) + + sig = inspect.signature(foo_partial) + + for ver in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(pickle_ver=ver, subclass=False): + sig_pickled = pickle.loads(pickle.dumps(sig, ver)) + self.assertEqual(sig, sig_pickled) + + # Test that basic sub-classing works + sig = inspect.signature(foo) + myparam = MyParameter(name='z', kind=inspect.Parameter.POSITIONAL_ONLY) + myparams = collections.OrderedDict(sig.parameters, a=myparam) + mysig = MySignature().replace(parameters=myparams.values(), + return_annotation=sig.return_annotation) + self.assertTrue(isinstance(mysig, MySignature)) + self.assertTrue(isinstance(mysig.parameters['z'], MyParameter)) + + for ver in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(pickle_ver=ver, subclass=True): + sig_pickled = pickle.loads(pickle.dumps(mysig, ver)) + self.assertEqual(mysig, sig_pickled) + self.assertTrue(isinstance(sig_pickled, MySignature)) + self.assertTrue(isinstance(sig_pickled.parameters['z'], + MyParameter)) + def test_signature_immutability(self): def test(a): pass @@ -1805,6 +2014,8 @@ class TestSignatureObject(unittest.TestCase): test_unbound_method(dict.__delitem__) test_unbound_method(property.__delete__) + # Regression test for issue #20586 + test_callable(_testcapi.docstring_with_signature_but_no_doc) @cpython_only @unittest.skipIf(MISSING_C_DOCSTRINGS, @@ -1824,23 +2035,26 @@ class TestSignatureObject(unittest.TestCase): self.assertEqual(inspect.signature(func), inspect.signature(decorated_func)) + def wrapper_like(*args, **kwargs) -> int: pass + self.assertEqual(inspect.signature(decorated_func, + follow_wrapped=False), + inspect.signature(wrapper_like)) + @cpython_only def test_signature_on_builtins_no_signature(self): import _testcapi - with self.assertRaisesRegex(ValueError, 'no signature found for builtin'): + with self.assertRaisesRegex(ValueError, + 'no signature found for builtin'): inspect.signature(_testcapi.docstring_no_signature) + with self.assertRaisesRegex(ValueError, + 'no signature found for builtin'): + inspect.signature(str) + def test_signature_on_non_function(self): with self.assertRaisesRegex(TypeError, 'is not a callable object'): inspect.signature(42) - with self.assertRaisesRegex(TypeError, 'is not a Python function'): - inspect.Signature.from_function(42) - - def test_signature_from_builtin_errors(self): - with self.assertRaisesRegex(TypeError, 'is not a Python builtin'): - inspect.Signature.from_builtin(42) - def test_signature_from_functionlike_object(self): def func(a,b, *args, kwonly=True, kwonlyreq, **kwargs): pass @@ -1861,9 +2075,9 @@ class TestSignatureObject(unittest.TestCase): def __call__(self, *args, **kwargs): return self.func(*args, **kwargs) - sig_func = inspect.Signature.from_function(func) + sig_func = inspect.Signature.from_callable(func) - sig_funclike = inspect.Signature.from_function(funclike(func)) + sig_funclike = inspect.Signature.from_callable(funclike(func)) self.assertEqual(sig_funclike, sig_func) sig_funclike = inspect.signature(funclike(func)) @@ -1911,9 +2125,6 @@ class TestSignatureObject(unittest.TestCase): __defaults__ = func.__defaults__ __kwdefaults__ = func.__kwdefaults__ - with self.assertRaisesRegex(TypeError, 'is not a Python function'): - inspect.Signature.from_function(funclike) - self.assertEqual(str(inspect.signature(funclike)), '(marker)') def test_signature_on_method(self): @@ -2265,6 +2476,13 @@ class TestSignatureObject(unittest.TestCase): ('b', ..., ..., "positional_or_keyword")), ...)) + self.assertEqual(self.signature(Foo.bar, follow_wrapped=False), + ((('args', ..., ..., "var_positional"), + ('kwargs', ..., ..., "var_keyword")), + ...)) # functools.wraps will copy __annotations__ + # from "func" to "wrapper", hence no + # return_annotation + # Test that we handle method wrappers correctly def decorator(func): @functools.wraps(func) @@ -2471,60 +2689,102 @@ class TestSignatureObject(unittest.TestCase): def bar(a, *, b:int) -> float: pass self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def bar(a, *, b:int) -> int: pass self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def bar(a, *, b:int): pass self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def bar(a, *, b:int=42) -> float: pass self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def bar(a, *, c) -> float: pass self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def bar(a, b:int) -> float: pass self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def spam(b:int, a) -> float: pass self.assertFalse(inspect.signature(spam) == inspect.signature(bar)) self.assertTrue(inspect.signature(spam) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(spam)), hash(inspect.signature(bar))) def foo(*, a, b, c): pass def bar(*, c, b, a): pass self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def foo(*, a=1, b, c): pass def bar(*, c, b, a=1): pass self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def foo(pos, *, a=1, b, c): pass def bar(pos, *, c, b, a=1): pass self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def foo(pos, *, a, b, c): pass def bar(pos, *, c, b, a=1): pass self.assertFalse(inspect.signature(foo) == inspect.signature(bar)) self.assertTrue(inspect.signature(foo) != inspect.signature(bar)) + self.assertNotEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) def foo(pos, *args, a=42, b, c, **kwargs:int): pass def bar(pos, *args, c, b, a=42, **kwargs:int): pass self.assertTrue(inspect.signature(foo) == inspect.signature(bar)) self.assertFalse(inspect.signature(foo) != inspect.signature(bar)) + self.assertEqual( + hash(inspect.signature(foo)), hash(inspect.signature(bar))) + + def test_signature_hashable(self): + S = inspect.Signature + P = inspect.Parameter - def test_signature_unhashable(self): def foo(a): pass - sig = inspect.signature(foo) + foo_sig = inspect.signature(foo) + + manual_sig = S(parameters=[P('a', P.POSITIONAL_OR_KEYWORD)]) + + self.assertEqual(hash(foo_sig), hash(manual_sig)) + self.assertNotEqual(hash(foo_sig), + hash(manual_sig.replace(return_annotation='spam'))) + + def bar(a) -> 1: pass + self.assertNotEqual(hash(foo_sig), hash(inspect.signature(bar))) + + def foo(a={}): pass with self.assertRaisesRegex(TypeError, 'unhashable type'): - hash(sig) + hash(inspect.signature(foo)) + + def foo(a) -> {}: pass + with self.assertRaisesRegex(TypeError, 'unhashable type'): + hash(inspect.signature(foo)) def test_signature_str(self): def foo(a:int=1, *, b, c=None, **kwargs) -> 42: @@ -2598,6 +2858,19 @@ class TestSignatureObject(unittest.TestCase): self.assertEqual(self.signature(Spam.foo), self.signature(Ham.foo)) + def test_signature_from_callable_python_obj(self): + class MySignature(inspect.Signature): pass + def foo(a, *, b:1): pass + foo_sig = MySignature.from_callable(foo) + self.assertTrue(isinstance(foo_sig, MySignature)) + + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_signature_from_callable_builtin_obj(self): + class MySignature(inspect.Signature): pass + sig = MySignature.from_callable(_pickle.Pickler) + self.assertTrue(isinstance(sig, MySignature)) + class TestParameterObject(unittest.TestCase): def test_signature_parameter_kinds(self): @@ -2643,6 +2916,16 @@ class TestParameterObject(unittest.TestCase): p.replace(kind=inspect.Parameter.VAR_POSITIONAL) self.assertTrue(repr(p).startswith('<Parameter')) + self.assertTrue('"a=42"' in repr(p)) + + def test_signature_parameter_hashable(self): + P = inspect.Parameter + foo = P('foo', kind=P.POSITIONAL_ONLY) + self.assertEqual(hash(foo), hash(P('foo', kind=P.POSITIONAL_ONLY))) + self.assertNotEqual(hash(foo), hash(P('foo', kind=P.POSITIONAL_ONLY, + default=42))) + self.assertNotEqual(hash(foo), + hash(foo.replace(kind=P.VAR_POSITIONAL))) def test_signature_parameter_equality(self): P = inspect.Parameter @@ -2660,13 +2943,6 @@ class TestParameterObject(unittest.TestCase): self.assertFalse(p != P('foo', default=42, kind=inspect.Parameter.KEYWORD_ONLY)) - def test_signature_parameter_unhashable(self): - p = inspect.Parameter('foo', default=42, - kind=inspect.Parameter.KEYWORD_ONLY) - - with self.assertRaisesRegex(TypeError, 'unhashable type'): - hash(p) - def test_signature_parameter_replace(self): p = inspect.Parameter('foo', default=42, kind=inspect.Parameter.KEYWORD_ONLY) @@ -2735,7 +3011,9 @@ class TestSignatureBind(unittest.TestCase): self.call(test, 1) with self.assertRaisesRegex(TypeError, 'too many positional arguments'): self.call(test, 1, spam=10) - with self.assertRaisesRegex(TypeError, 'too many keyword arguments'): + with self.assertRaisesRegex( + TypeError, "got an unexpected keyword argument 'spam'"): + self.call(test, spam=1) def test_signature_bind_var(self): @@ -2760,10 +3038,12 @@ class TestSignatureBind(unittest.TestCase): with self.assertRaisesRegex(TypeError, 'too many positional arguments'): self.call(test, 1, 2, 3, 4) - with self.assertRaisesRegex(TypeError, "'b' parameter lacking default"): + with self.assertRaisesRegex(TypeError, + "missing a required argument: 'b'"): self.call(test, 1) - with self.assertRaisesRegex(TypeError, "'a' parameter lacking default"): + with self.assertRaisesRegex(TypeError, + "missing a required argument: 'a'"): self.call(test) def test(a, b, c=10): @@ -2836,7 +3116,7 @@ class TestSignatureBind(unittest.TestCase): def test(a, *, foo=1, bar): return foo with self.assertRaisesRegex(TypeError, - "'bar' parameter lacking default value"): + "missing a required argument: 'bar'"): self.call(test, 1) def test(foo, *, bar): @@ -2844,8 +3124,9 @@ class TestSignatureBind(unittest.TestCase): self.assertEqual(self.call(test, 1, bar=2), (1, 2)) self.assertEqual(self.call(test, bar=2, foo=1), (1, 2)) - with self.assertRaisesRegex(TypeError, - 'too many keyword arguments'): + with self.assertRaisesRegex( + TypeError, "got an unexpected keyword argument 'spam'"): + self.call(test, bar=2, foo=1, spam=10) with self.assertRaisesRegex(TypeError, @@ -2856,12 +3137,13 @@ class TestSignatureBind(unittest.TestCase): 'too many positional arguments'): self.call(test, 1, 2, bar=2) - with self.assertRaisesRegex(TypeError, - 'too many keyword arguments'): + with self.assertRaisesRegex( + TypeError, "got an unexpected keyword argument 'spam'"): + self.call(test, 1, bar=2, spam='ham') with self.assertRaisesRegex(TypeError, - "'bar' parameter lacking default value"): + "missing a required argument: 'bar'"): self.call(test, 1) def test(foo, *, bar, **bin): @@ -2873,7 +3155,7 @@ class TestSignatureBind(unittest.TestCase): self.assertEqual(self.call(test, spam='ham', foo=1, bar=2), (1, 2, {'spam': 'ham'})) with self.assertRaisesRegex(TypeError, - "'foo' parameter lacking default value"): + "missing a required argument: 'foo'"): self.call(test, spam='ham', bar=2) self.assertEqual(self.call(test, 1, bar=2, bin=1, spam=10), (1, 2, {'bin': 1, 'spam': 10})) @@ -2938,7 +3220,9 @@ class TestSignatureBind(unittest.TestCase): return a, args sig = inspect.signature(test) - with self.assertRaisesRegex(TypeError, "too many keyword arguments"): + with self.assertRaisesRegex( + TypeError, "got an unexpected keyword argument 'args'"): + sig.bind(a=0, args=1) def test(*args, **kwargs): @@ -2982,6 +3266,64 @@ class TestBoundArguments(unittest.TestCase): self.assertFalse(ba == ba4) self.assertTrue(ba != ba4) + def foo(*, a, b): pass + sig = inspect.signature(foo) + ba1 = sig.bind(a=1, b=2) + ba2 = sig.bind(b=2, a=1) + self.assertTrue(ba1 == ba2) + self.assertFalse(ba1 != ba2) + + def test_signature_bound_arguments_pickle(self): + def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass + sig = inspect.signature(foo) + ba = sig.bind(20, 30, z={}) + + for ver in range(pickle.HIGHEST_PROTOCOL + 1): + with self.subTest(pickle_ver=ver): + ba_pickled = pickle.loads(pickle.dumps(ba, ver)) + self.assertEqual(ba, ba_pickled) + + def test_signature_bound_arguments_repr(self): + def foo(a, b, *, c:1={}, **kw) -> {42:'ham'}: pass + sig = inspect.signature(foo) + ba = sig.bind(20, 30, z={}) + self.assertRegex(repr(ba), r'<BoundArguments \(a=20,.*\}\}\)>') + + def test_signature_bound_arguments_apply_defaults(self): + def foo(a, b=1, *args, c:1={}, **kw): pass + sig = inspect.signature(foo) + + ba = sig.bind(20) + ba.apply_defaults() + self.assertEqual( + list(ba.arguments.items()), + [('a', 20), ('b', 1), ('args', ()), ('c', {}), ('kw', {})]) + + # Make sure that we preserve the order: + # i.e. 'c' should be *before* 'kw'. + ba = sig.bind(10, 20, 30, d=1) + ba.apply_defaults() + self.assertEqual( + list(ba.arguments.items()), + [('a', 10), ('b', 20), ('args', (30,)), ('c', {}), ('kw', {'d':1})]) + + # Make sure that BoundArguments produced by bind_partial() + # are supported. + def foo(a, b): pass + sig = inspect.signature(foo) + ba = sig.bind_partial(20) + ba.apply_defaults() + self.assertEqual( + list(ba.arguments.items()), + [('a', 20)]) + + # Test no args + def foo(): pass + sig = inspect.signature(foo) + ba = sig.bind() + ba.apply_defaults() + self.assertEqual(list(ba.arguments.items()), []) + class TestSignaturePrivateHelpers(unittest.TestCase): def test_signature_get_bound_param(self): @@ -3046,6 +3388,61 @@ class TestSignaturePrivateHelpers(unittest.TestCase): None, None) +class TestSignatureDefinitions(unittest.TestCase): + # This test case provides a home for checking that particular APIs + # have signatures available for introspection + + @cpython_only + @unittest.skipIf(MISSING_C_DOCSTRINGS, + "Signature information for builtins requires docstrings") + def test_builtins_have_signatures(self): + # This checks all builtin callables in CPython have signatures + # A few have signatures Signature can't yet handle, so we skip those + # since they will have to wait until PEP 457 adds the required + # introspection support to the inspect module + # Some others also haven't been converted yet for various other + # reasons, so we also skip those for the time being, but design + # the test to fail in order to indicate when it needs to be + # updated. + no_signature = set() + # These need PEP 457 groups + needs_groups = {"range", "slice", "dir", "getattr", + "next", "iter", "vars"} + no_signature |= needs_groups + # These need PEP 457 groups or a signature change to accept None + needs_semantic_update = {"round"} + no_signature |= needs_semantic_update + # These need *args support in Argument Clinic + needs_varargs = {"min", "max", "print", "__build_class__"} + no_signature |= needs_varargs + # These simply weren't covered in the initial AC conversion + # for builtin callables + not_converted_yet = {"open", "__import__"} + no_signature |= not_converted_yet + # These builtin types are expected to provide introspection info + types_with_signatures = set() + # Check the signatures we expect to be there + ns = vars(builtins) + for name, obj in sorted(ns.items()): + if not callable(obj): + continue + # The builtin types haven't been converted to AC yet + if isinstance(obj, type) and (name not in types_with_signatures): + # Note that this also skips all the exception types + no_signature.add(name) + if (name in no_signature): + # Not yet converted + continue + with self.subTest(builtin=name): + self.assertIsNotNone(inspect.signature(obj)) + # Check callables that haven't been converted don't claim a signature + # This ensures this test will start failing as more signatures are + # added, so the affected items can be moved into the scope of the + # regression test above + for name in no_signature: + with self.subTest(builtin=name): + self.assertIsNone(obj.__text_signature__) + class TestUnwrap(unittest.TestCase): @@ -3187,8 +3584,10 @@ def test_main(): TestGetcallargsFunctions, TestGetcallargsMethods, TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState, TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject, - TestBoundArguments, TestSignaturePrivateHelpers, TestGetClosureVars, - TestUnwrap, TestMain, TestReload + TestBoundArguments, TestSignaturePrivateHelpers, + TestSignatureDefinitions, + TestGetClosureVars, TestUnwrap, TestMain, TestReload, + TestGetCoroutineState ) if __name__ == "__main__": |