summaryrefslogtreecommitdiffstats
path: root/Lib/test/test_inspect.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/test/test_inspect.py')
-rw-r--r--Lib/test/test_inspect.py1090
1 files changed, 985 insertions, 105 deletions
diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py
index 5cbec9b..1a124b5 100644
--- a/Lib/test/test_inspect.py
+++ b/Lib/test/test_inspect.py
@@ -1,22 +1,35 @@
-import re
-import sys
-import types
-import unittest
+import collections
+import datetime
+import functools
+import importlib
import inspect
+import io
import linecache
-import datetime
-import collections
import os
-import shutil
from os.path import normcase
+import _pickle
+import re
+import shutil
+import sys
+import types
+import textwrap
+import unicodedata
+import unittest
+import unittest.mock
-from test.support import run_unittest, TESTFN, DirsOnSysPath
+try:
+ from concurrent.futures import ThreadPoolExecutor
+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 import inspect_fodder as mod
from test import inspect_fodder2 as mod2
-# C module for test_findsource_binary
-import unicodedata
+from test.test_import import _ready_to_import
+
# Functions tested in this suite:
# ismodule, isclass, ismethod, isfunction, istraceback, isframe, iscode,
@@ -63,6 +76,10 @@ def generator_function_example(self):
for i in range(2):
yield i
+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')])
@@ -120,7 +137,6 @@ class TestPredicates(IsTestBase):
def test_get_slot_members(self):
class C(object):
__slots__ = ("a", "b")
-
x = C()
x.a = 42
members = dict(inspect.getmembers(x))
@@ -310,6 +326,16 @@ class TestRetrievingSourceCode(GetSourceBase):
def test_getfile(self):
self.assertEqual(inspect.getfile(mod.StupidGit), mod.__file__)
+ def test_getfile_class_without_module(self):
+ class CM(type):
+ @property
+ def __module__(cls):
+ raise AttributeError
+ class C(metaclass=CM):
+ pass
+ with self.assertRaises(TypeError):
+ inspect.getfile(C)
+
def test_getmodule_recursion(self):
from types import ModuleType
name = '__inspect_dummy'
@@ -413,19 +439,20 @@ class TestBuggyCases(GetSourceBase):
def test_method_in_dynamic_class(self):
self.assertSourceEqual(mod2.method_in_dynamic_class, 95, 97)
- @unittest.skipIf(
- not hasattr(unicodedata, '__file__') or
- unicodedata.__file__[-4:] in (".pyc", ".pyo"),
- "unicodedata is not an external binary module")
+ # This should not skip for CPython, but might on a repackaged python where
+ # unicodedata is not an external module, or on pypy.
+ @unittest.skipIf(not hasattr(unicodedata, '__file__') or
+ unicodedata.__file__.endswith('.py'),
+ "unicodedata is not an external binary module")
def test_findsource_binary(self):
- self.assertRaises(IOError, inspect.getsource, unicodedata)
- self.assertRaises(IOError, inspect.findsource, unicodedata)
+ self.assertRaises(OSError, inspect.getsource, unicodedata)
+ self.assertRaises(OSError, inspect.findsource, unicodedata)
def test_findsource_code_in_linecache(self):
lines = ["x=1"]
co = compile(lines[0], "_dynamically_created_file", "exec")
- self.assertRaises(IOError, inspect.findsource, co)
- self.assertRaises(IOError, inspect.getsource, co)
+ self.assertRaises(OSError, inspect.findsource, co)
+ self.assertRaises(OSError, inspect.getsource, co)
linecache.cache[co.co_filename] = (1, None, lines, co.co_filename)
try:
self.assertEqual(inspect.findsource(co), (lines,0))
@@ -463,13 +490,13 @@ class _BrokenDataDescriptor(object):
A broken data descriptor. See bug #1785.
"""
def __get__(*args):
- raise AssertionError("should not __get__ data descriptors")
+ raise AttributeError("broken data descriptor")
def __set__(*args):
raise RuntimeError
def __getattr__(*args):
- raise AssertionError("should not __getattr__ data descriptors")
+ raise AttributeError("broken data descriptor")
class _BrokenMethodDescriptor(object):
@@ -477,10 +504,10 @@ class _BrokenMethodDescriptor(object):
A broken method descriptor. See bug #1785.
"""
def __get__(*args):
- raise AssertionError("should not __get__ method descriptors")
+ raise AttributeError("broken method descriptor")
def __getattr__(*args):
- raise AssertionError("should not __getattr__ method descriptors")
+ raise AttributeError("broken method descriptor")
# Helper for testing classify_class_attrs.
@@ -559,6 +586,96 @@ class TestClassesAndFunctions(unittest.TestCase):
kwonlyargs_e=['arg'],
formatted='(*, arg)')
+ def test_argspec_api_ignores_wrapped(self):
+ # Issue 20684: low level introspection API must ignore __wrapped__
+ @functools.wraps(mod.spam)
+ def ham(x, y):
+ pass
+ # Basic check
+ self.assertArgSpecEquals(ham, ['x', 'y'], formatted='(x, y)')
+ self.assertFullArgSpecEquals(ham, ['x', 'y'], formatted='(x, y)')
+ self.assertFullArgSpecEquals(functools.partial(ham),
+ ['x', 'y'], formatted='(x, y)')
+ # Other variants
+ def check_method(f):
+ self.assertArgSpecEquals(f, ['self', 'x', 'y'],
+ formatted='(self, x, y)')
+ class C:
+ @functools.wraps(mod.spam)
+ def ham(self, x, y):
+ pass
+ pham = functools.partialmethod(ham)
+ @functools.wraps(mod.spam)
+ def __call__(self, x, y):
+ pass
+ check_method(C())
+ check_method(C.ham)
+ check_method(C().ham)
+ check_method(C.pham)
+ check_method(C().pham)
+
+ class C_new:
+ @functools.wraps(mod.spam)
+ def __new__(self, x, y):
+ pass
+ check_method(C_new)
+
+ class C_init:
+ @functools.wraps(mod.spam)
+ def __init__(self, x, y):
+ pass
+ check_method(C_init)
+
+ def test_getfullargspec_signature_attr(self):
+ def test():
+ pass
+ spam_param = inspect.Parameter('spam', inspect.Parameter.POSITIONAL_ONLY)
+ test.__signature__ = inspect.Signature(parameters=(spam_param,))
+
+ self.assertFullArgSpecEquals(test, args_e=['spam'], formatted='(spam)')
+
+ def test_getfullargspec_signature_annos(self):
+ def test(a:'spam') -> 'ham': pass
+ spec = inspect.getfullargspec(test)
+ self.assertEqual(test.__annotations__, spec.annotations)
+
+ def test(): pass
+ spec = inspect.getfullargspec(test)
+ self.assertEqual(test.__annotations__, spec.annotations)
+
+ @unittest.skipIf(MISSING_C_DOCSTRINGS,
+ "Signature information for builtins requires docstrings")
+ def test_getfullargspec_builtin_methods(self):
+ self.assertFullArgSpecEquals(_pickle.Pickler.dump,
+ args_e=['self', 'obj'], formatted='(self, obj)')
+
+ self.assertFullArgSpecEquals(_pickle.Pickler(io.BytesIO()).dump,
+ args_e=['self', 'obj'], formatted='(self, obj)')
+
+ self.assertFullArgSpecEquals(
+ os.stat,
+ args_e=['path'],
+ kwonlyargs_e=['dir_fd', 'follow_symlinks'],
+ kwonlydefaults_e={'dir_fd': None, 'follow_symlinks': True},
+ formatted='(path, *, dir_fd=None, follow_symlinks=True)')
+
+ @cpython_only
+ @unittest.skipIf(MISSING_C_DOCSTRINGS,
+ "Signature information for builtins requires docstrings")
+ def test_getfullagrspec_builtin_func(self):
+ import _testcapi
+ builtin = _testcapi.docstring_with_signature_with_defaults
+ spec = inspect.getfullargspec(builtin)
+ self.assertEqual(spec.defaults[0], 'avocado')
+
+ @cpython_only
+ @unittest.skipIf(MISSING_C_DOCSTRINGS,
+ "Signature information for builtins requires docstrings")
+ def test_getfullagrspec_builtin_func_no_signature(self):
+ import _testcapi
+ builtin = _testcapi.docstring_no_signature
+ with self.assertRaises(TypeError):
+ inspect.getfullargspec(builtin)
def test_getargspec_method(self):
class A(object):
@@ -588,6 +705,10 @@ class TestClassesAndFunctions(unittest.TestCase):
md = _BrokenMethodDescriptor()
attrs = attrs_wo_objs(A)
+
+ self.assertIn(('__new__', 'method', object), attrs, 'missing __new__')
+ self.assertIn(('__init__', 'method', object), attrs, 'missing __init__')
+
self.assertIn(('s', 'static method', A), attrs, 'missing static method')
self.assertIn(('c', 'class method', A), attrs, 'missing class method')
self.assertIn(('p', 'property', A), attrs, 'missing property')
@@ -650,6 +771,103 @@ class TestClassesAndFunctions(unittest.TestCase):
if isinstance(builtin, type):
inspect.classify_class_attrs(builtin)
+ def test_classify_DynamicClassAttribute(self):
+ class Meta(type):
+ def __getattr__(self, name):
+ if name == 'ham':
+ return 'spam'
+ return super().__getattr__(name)
+ class VA(metaclass=Meta):
+ @types.DynamicClassAttribute
+ def ham(self):
+ return 'eggs'
+ should_find_dca = inspect.Attribute('ham', 'data', VA, VA.__dict__['ham'])
+ self.assertIn(should_find_dca, inspect.classify_class_attrs(VA))
+ should_find_ga = inspect.Attribute('ham', 'data', Meta, 'spam')
+ self.assertIn(should_find_ga, inspect.classify_class_attrs(VA))
+
+ def test_classify_overrides_bool(self):
+ class NoBool(object):
+ def __eq__(self, other):
+ return NoBool()
+
+ def __bool__(self):
+ raise NotImplementedError(
+ "This object does not specify a boolean value")
+
+ class HasNB(object):
+ dd = NoBool()
+
+ should_find_attr = inspect.Attribute('dd', 'data', HasNB, HasNB.dd)
+ self.assertIn(should_find_attr, inspect.classify_class_attrs(HasNB))
+
+ def test_classify_metaclass_class_attribute(self):
+ class Meta(type):
+ fish = 'slap'
+ def __dir__(self):
+ return ['__class__', '__module__', '__name__', 'fish']
+ class Class(metaclass=Meta):
+ pass
+ should_find = inspect.Attribute('fish', 'data', Meta, 'slap')
+ self.assertIn(should_find, inspect.classify_class_attrs(Class))
+
+ def test_classify_VirtualAttribute(self):
+ class Meta(type):
+ def __dir__(cls):
+ return ['__class__', '__module__', '__name__', 'BOOM']
+ def __getattr__(self, name):
+ if name =='BOOM':
+ return 42
+ return super().__getattr(name)
+ class Class(metaclass=Meta):
+ pass
+ should_find = inspect.Attribute('BOOM', 'data', Meta, 42)
+ self.assertIn(should_find, inspect.classify_class_attrs(Class))
+
+ def test_classify_VirtualAttribute_multi_classes(self):
+ class Meta1(type):
+ def __dir__(cls):
+ return ['__class__', '__module__', '__name__', 'one']
+ def __getattr__(self, name):
+ if name =='one':
+ return 1
+ return super().__getattr__(name)
+ class Meta2(type):
+ def __dir__(cls):
+ return ['__class__', '__module__', '__name__', 'two']
+ def __getattr__(self, name):
+ if name =='two':
+ return 2
+ return super().__getattr__(name)
+ class Meta3(Meta1, Meta2):
+ def __dir__(cls):
+ return list(sorted(set(['__class__', '__module__', '__name__', 'three'] +
+ Meta1.__dir__(cls) + Meta2.__dir__(cls))))
+ def __getattr__(self, name):
+ if name =='three':
+ return 3
+ return super().__getattr__(name)
+ class Class1(metaclass=Meta1):
+ pass
+ class Class2(Class1, metaclass=Meta3):
+ pass
+
+ should_find1 = inspect.Attribute('one', 'data', Meta1, 1)
+ should_find2 = inspect.Attribute('two', 'data', Meta2, 2)
+ should_find3 = inspect.Attribute('three', 'data', Meta3, 3)
+ cca = inspect.classify_class_attrs(Class2)
+ for sf in (should_find1, should_find2, should_find3):
+ self.assertIn(sf, cca)
+
+ def test_classify_class_attrs_with_buggy_dir(self):
+ class M(type):
+ def __dir__(cls):
+ return ['__class__', '__name__', 'missing']
+ class C(metaclass=M):
+ pass
+ attrs = [a[0] for a in inspect.classify_class_attrs(C)]
+ self.assertNotIn('missing', attrs)
+
def test_getmembers_descriptors(self):
class A(object):
dd = _BrokenDataDescriptor()
@@ -693,6 +911,28 @@ class TestClassesAndFunctions(unittest.TestCase):
self.assertIn(('f', b.f), inspect.getmembers(b))
self.assertIn(('f', b.f), inspect.getmembers(b, inspect.ismethod))
+ def test_getmembers_VirtualAttribute(self):
+ class M(type):
+ def __getattr__(cls, name):
+ if name == 'eggs':
+ return 'scrambled'
+ return super().__getattr__(name)
+ class A(metaclass=M):
+ @types.DynamicClassAttribute
+ def eggs(self):
+ return 'spam'
+ self.assertIn(('eggs', 'scrambled'), inspect.getmembers(A))
+ self.assertIn(('eggs', 'spam'), inspect.getmembers(A()))
+
+ def test_getmembers_with_buggy_dir(self):
+ class M(type):
+ def __dir__(cls):
+ return ['__class__', '__name__', 'missing']
+ class C(metaclass=M):
+ pass
+ attrs = [a[0] for a in inspect.getmembers(C)]
+ self.assertNotIn('missing', attrs)
+
_global_ref = object()
class TestGetClosureVars(unittest.TestCase):
@@ -989,6 +1229,20 @@ class TestGetcallargsFunctions(unittest.TestCase):
self.assertEqualException(f3, '1, 2')
self.assertEqualException(f3, '1, 2, a=1, b=2')
+ # issue #20816: getcallargs() fails to iterate over non-existent
+ # kwonlydefaults and raises a wrong TypeError
+ def f5(*, a): pass
+ with self.assertRaisesRegex(TypeError,
+ 'missing 1 required keyword-only'):
+ inspect.getcallargs(f5)
+
+
+ # issue20817:
+ def f6(a, b, c):
+ pass
+ with self.assertRaisesRegex(TypeError, "'a', 'b' and 'c'"):
+ inspect.getcallargs(f6)
+
class TestGetcallargsMethods(TestGetcallargsFunctions):
def setUp(self):
@@ -1080,6 +1334,15 @@ class TestGetattrStatic(unittest.TestCase):
self.assertEqual(inspect.getattr_static(Thing, 'x'), Thing.x)
+ def test_classVirtualAttribute(self):
+ class Thing(object):
+ @types.DynamicClassAttribute
+ def x(self):
+ return self._x
+ _x = object()
+
+ self.assertEqual(inspect.getattr_static(Thing, 'x'), Thing.__dict__['x'])
+
def test_inherited_classattribute(self):
class Thing(object):
x = object()
@@ -1390,11 +1653,13 @@ class TestSignatureObject(unittest.TestCase):
self.assertEqual(str(S()), '()')
- def test(po, pk, *args, ko, **kwargs):
+ def test(po, pk, pod=42, pkd=100, *args, ko, **kwargs):
pass
sig = inspect.signature(test)
po = sig.parameters['po'].replace(kind=P.POSITIONAL_ONLY)
+ pod = sig.parameters['pod'].replace(kind=P.POSITIONAL_ONLY)
pk = sig.parameters['pk']
+ pkd = sig.parameters['pkd']
args = sig.parameters['args']
ko = sig.parameters['ko']
kwargs = sig.parameters['kwargs']
@@ -1417,6 +1682,15 @@ class TestSignatureObject(unittest.TestCase):
with self.assertRaisesRegex(ValueError, 'duplicate parameter name'):
S((po, pk, args, kwargs2, ko))
+ with self.assertRaisesRegex(ValueError, 'follows default argument'):
+ S((pod, po))
+
+ with self.assertRaisesRegex(ValueError, 'follows default argument'):
+ S((po, pkd, pk))
+
+ with self.assertRaisesRegex(ValueError, 'follows default argument'):
+ S((pkd, pk))
+
def test_signature_immutability(self):
def test(a):
pass
@@ -1461,19 +1735,100 @@ class TestSignatureObject(unittest.TestCase):
('kwargs', ..., int, "var_keyword")),
...))
- def test_signature_on_builtin_function(self):
- with self.assertRaisesRegex(ValueError, 'not supported by signature'):
- inspect.signature(type)
- with self.assertRaisesRegex(ValueError, 'not supported by signature'):
- # support for 'wrapper_descriptor'
- inspect.signature(type.__call__)
- with self.assertRaisesRegex(ValueError, 'not supported by signature'):
- # support for 'method-wrapper'
- inspect.signature(min.__call__)
- with self.assertRaisesRegex(ValueError,
- 'no signature found for builtin function'):
- # support for 'method-wrapper'
- inspect.signature(min)
+ @cpython_only
+ @unittest.skipIf(MISSING_C_DOCSTRINGS,
+ "Signature information for builtins requires docstrings")
+ def test_signature_on_builtins(self):
+ import _testcapi
+
+ def test_unbound_method(o):
+ """Use this to test unbound methods (things that should have a self)"""
+ signature = inspect.signature(o)
+ self.assertTrue(isinstance(signature, inspect.Signature))
+ self.assertEqual(list(signature.parameters.values())[0].name, 'self')
+ return signature
+
+ def test_callable(o):
+ """Use this to test bound methods or normal callables (things that don't expect self)"""
+ signature = inspect.signature(o)
+ self.assertTrue(isinstance(signature, inspect.Signature))
+ if signature.parameters:
+ self.assertNotEqual(list(signature.parameters.values())[0].name, 'self')
+ return signature
+
+ signature = test_callable(_testcapi.docstring_with_signature_with_defaults)
+ def p(name): return signature.parameters[name].default
+ self.assertEqual(p('s'), 'avocado')
+ self.assertEqual(p('b'), b'bytes')
+ self.assertEqual(p('d'), 3.14)
+ self.assertEqual(p('i'), 35)
+ self.assertEqual(p('n'), None)
+ self.assertEqual(p('t'), True)
+ self.assertEqual(p('f'), False)
+ self.assertEqual(p('local'), 3)
+ self.assertEqual(p('sys'), sys.maxsize)
+ self.assertEqual(p('exp'), sys.maxsize - 1)
+
+ test_callable(object)
+
+ # normal method
+ # (PyMethodDescr_Type, "method_descriptor")
+ test_unbound_method(_pickle.Pickler.dump)
+ d = _pickle.Pickler(io.StringIO())
+ test_callable(d.dump)
+
+ # static method
+ test_callable(str.maketrans)
+ test_callable('abc'.maketrans)
+
+ # class method
+ test_callable(dict.fromkeys)
+ test_callable({}.fromkeys)
+
+ # wrapper around slot (PyWrapperDescr_Type, "wrapper_descriptor")
+ test_unbound_method(type.__call__)
+ test_unbound_method(int.__add__)
+ test_callable((3).__add__)
+
+ # _PyMethodWrapper_Type
+ # support for 'method-wrapper'
+ test_callable(min.__call__)
+
+ # This doesn't work now.
+ # (We don't have a valid signature for "type" in 3.4)
+ with self.assertRaisesRegex(ValueError, "no signature found"):
+ class ThisWorksNow:
+ __call__ = type
+ test_callable(ThisWorksNow())
+
+ # Regression test for issue #20786
+ test_unbound_method(dict.__delitem__)
+ test_unbound_method(property.__delete__)
+
+
+ @cpython_only
+ @unittest.skipIf(MISSING_C_DOCSTRINGS,
+ "Signature information for builtins requires docstrings")
+ def test_signature_on_decorated_builtins(self):
+ import _testcapi
+ func = _testcapi.docstring_with_signature_with_defaults
+
+ def decorator(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs) -> int:
+ return func(*args, **kwargs)
+ return wrapper
+
+ decorated_func = decorator(func)
+
+ self.assertEqual(inspect.signature(func),
+ inspect.signature(decorated_func))
+
+ @cpython_only
+ def test_signature_on_builtins_no_signature(self):
+ import _testcapi
+ with self.assertRaisesRegex(ValueError, 'no signature found for builtin'):
+ inspect.signature(_testcapi.docstring_no_signature)
def test_signature_on_non_function(self):
with self.assertRaisesRegex(TypeError, 'is not a callable object'):
@@ -1482,14 +1837,121 @@ class TestSignatureObject(unittest.TestCase):
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
+
+ class funclike:
+ # Has to be callable, and have correct
+ # __code__, __annotations__, __defaults__, __name__,
+ # and __kwdefaults__ attributes
+
+ def __init__(self, func):
+ self.__name__ = func.__name__
+ self.__code__ = func.__code__
+ self.__annotations__ = func.__annotations__
+ self.__defaults__ = func.__defaults__
+ self.__kwdefaults__ = func.__kwdefaults__
+ self.func = func
+
+ def __call__(self, *args, **kwargs):
+ return self.func(*args, **kwargs)
+
+ sig_func = inspect.Signature.from_function(func)
+
+ sig_funclike = inspect.Signature.from_function(funclike(func))
+ self.assertEqual(sig_funclike, sig_func)
+
+ sig_funclike = inspect.signature(funclike(func))
+ self.assertEqual(sig_funclike, sig_func)
+
+ # If object is not a duck type of function, then
+ # signature will try to get a signature for its '__call__'
+ # method
+ fl = funclike(func)
+ del fl.__defaults__
+ self.assertEqual(self.signature(fl),
+ ((('args', ..., ..., "var_positional"),
+ ('kwargs', ..., ..., "var_keyword")),
+ ...))
+
+ # Test with cython-like builtins:
+ _orig_isdesc = inspect.ismethoddescriptor
+ def _isdesc(obj):
+ if hasattr(obj, '_builtinmock'):
+ return True
+ return _orig_isdesc(obj)
+
+ with unittest.mock.patch('inspect.ismethoddescriptor', _isdesc):
+ builtin_func = funclike(func)
+ # Make sure that our mock setup is working
+ self.assertFalse(inspect.ismethoddescriptor(builtin_func))
+ builtin_func._builtinmock = True
+ self.assertTrue(inspect.ismethoddescriptor(builtin_func))
+ self.assertEqual(inspect.signature(builtin_func), sig_func)
+
+ def test_signature_functionlike_class(self):
+ # We only want to duck type function-like objects,
+ # not classes.
+
+ def func(a,b, *args, kwonly=True, kwonlyreq, **kwargs):
+ pass
+
+ class funclike:
+ def __init__(self, marker):
+ pass
+
+ __name__ = func.__name__
+ __code__ = func.__code__
+ __annotations__ = func.__annotations__
+ __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):
class Test:
- def foo(self, arg1, arg2=1) -> int:
+ def __init__(*args):
+ pass
+ def m1(self, arg1, arg2=1) -> int:
+ pass
+ def m2(*args):
+ pass
+ def __call__(*, a):
pass
- meth = Test().foo
+ self.assertEqual(self.signature(Test().m1),
+ ((('arg1', ..., ..., "positional_or_keyword"),
+ ('arg2', 1, ..., "positional_or_keyword")),
+ int))
- self.assertEqual(self.signature(meth),
+ self.assertEqual(self.signature(Test().m2),
+ ((('args', ..., ..., "var_positional"),),
+ ...))
+
+ self.assertEqual(self.signature(Test),
+ ((('args', ..., ..., "var_positional"),),
+ ...))
+
+ with self.assertRaisesRegex(ValueError, 'invalid method signature'):
+ self.signature(Test())
+
+ def test_signature_wrapped_bound_method(self):
+ # Issue 24298
+ class Test:
+ def m1(self, arg1, arg2=1) -> int:
+ pass
+ @functools.wraps(Test().m1)
+ def m1d(*args, **kwargs):
+ pass
+ self.assertEqual(self.signature(m1d),
((('arg1', ..., ..., "positional_or_keyword"),
('arg2', 1, ..., "positional_or_keyword")),
int))
@@ -1533,6 +1995,8 @@ class TestSignatureObject(unittest.TestCase):
def test_signature_on_partial(self):
from functools import partial
+ Parameter = inspect.Parameter
+
def test():
pass
@@ -1568,15 +2032,22 @@ class TestSignatureObject(unittest.TestCase):
self.assertEqual(self.signature(partial(test, b=1, c=2)),
((('a', ..., ..., "positional_or_keyword"),
- ('b', 1, ..., "positional_or_keyword"),
+ ('b', 1, ..., "keyword_only"),
('c', 2, ..., "keyword_only"),
('d', ..., ..., "keyword_only")),
...))
self.assertEqual(self.signature(partial(test, 0, b=1, c=2)),
- ((('b', 1, ..., "positional_or_keyword"),
+ ((('b', 1, ..., "keyword_only"),
('c', 2, ..., "keyword_only"),
- ('d', ..., ..., "keyword_only"),),
+ ('d', ..., ..., "keyword_only")),
+ ...))
+
+ self.assertEqual(self.signature(partial(test, a=1)),
+ ((('a', 1, ..., "keyword_only"),
+ ('b', ..., ..., "keyword_only"),
+ ('c', ..., ..., "keyword_only"),
+ ('d', ..., ..., "keyword_only")),
...))
def test(a, *args, b, **kwargs):
@@ -1588,13 +2059,18 @@ class TestSignatureObject(unittest.TestCase):
('kwargs', ..., ..., "var_keyword")),
...))
+ self.assertEqual(self.signature(partial(test, a=1)),
+ ((('a', 1, ..., "keyword_only"),
+ ('b', ..., ..., "keyword_only"),
+ ('kwargs', ..., ..., "var_keyword")),
+ ...))
+
self.assertEqual(self.signature(partial(test, 1, 2, 3)),
((('args', ..., ..., "var_positional"),
('b', ..., ..., "keyword_only"),
('kwargs', ..., ..., "var_keyword")),
...))
-
self.assertEqual(self.signature(partial(test, 1, 2, 3, test=True)),
((('args', ..., ..., "var_positional"),
('b', ..., ..., "keyword_only"),
@@ -1641,7 +2117,7 @@ class TestSignatureObject(unittest.TestCase):
return a
_foo = partial(partial(foo, a=10), a=20)
self.assertEqual(self.signature(_foo),
- ((('a', 20, ..., "positional_or_keyword"),),
+ ((('a', 20, ..., "keyword_only"),),
...))
# check that we don't have any side-effects in signature(),
# and the partial object is still functioning
@@ -1650,42 +2126,119 @@ class TestSignatureObject(unittest.TestCase):
def foo(a, b, c):
return a, b, c
_foo = partial(partial(foo, 1, b=20), b=30)
+
self.assertEqual(self.signature(_foo),
- ((('b', 30, ..., "positional_or_keyword"),
- ('c', ..., ..., "positional_or_keyword")),
+ ((('b', 30, ..., "keyword_only"),
+ ('c', ..., ..., "keyword_only")),
...))
self.assertEqual(_foo(c=10), (1, 30, 10))
- _foo = partial(_foo, 2) # now 'b' has two values -
- # positional and keyword
- with self.assertRaisesRegex(ValueError, "has incorrect arguments"):
- inspect.signature(_foo)
def foo(a, b, c, *, d):
return a, b, c, d
_foo = partial(partial(foo, d=20, c=20), b=10, d=30)
self.assertEqual(self.signature(_foo),
((('a', ..., ..., "positional_or_keyword"),
- ('b', 10, ..., "positional_or_keyword"),
- ('c', 20, ..., "positional_or_keyword"),
- ('d', 30, ..., "keyword_only")),
+ ('b', 10, ..., "keyword_only"),
+ ('c', 20, ..., "keyword_only"),
+ ('d', 30, ..., "keyword_only"),
+ ),
...))
ba = inspect.signature(_foo).bind(a=200, b=11)
self.assertEqual(_foo(*ba.args, **ba.kwargs), (200, 11, 20, 30))
def foo(a=1, b=2, c=3):
return a, b, c
- _foo = partial(foo, a=10, c=13)
- ba = inspect.signature(_foo).bind(11)
+ _foo = partial(foo, c=13) # (a=1, b=2, *, c=13)
+
+ ba = inspect.signature(_foo).bind(a=11)
self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 2, 13))
+
ba = inspect.signature(_foo).bind(11, 12)
self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13))
+
ba = inspect.signature(_foo).bind(11, b=12)
self.assertEqual(_foo(*ba.args, **ba.kwargs), (11, 12, 13))
+
ba = inspect.signature(_foo).bind(b=12)
- self.assertEqual(_foo(*ba.args, **ba.kwargs), (10, 12, 13))
- _foo = partial(_foo, b=10)
- ba = inspect.signature(_foo).bind(12, 14)
- self.assertEqual(_foo(*ba.args, **ba.kwargs), (12, 14, 13))
+ self.assertEqual(_foo(*ba.args, **ba.kwargs), (1, 12, 13))
+
+ _foo = partial(_foo, b=10, c=20)
+ ba = inspect.signature(_foo).bind(12)
+ self.assertEqual(_foo(*ba.args, **ba.kwargs), (12, 10, 20))
+
+
+ def foo(a, b, c, d, **kwargs):
+ pass
+ sig = inspect.signature(foo)
+ params = sig.parameters.copy()
+ params['a'] = params['a'].replace(kind=Parameter.POSITIONAL_ONLY)
+ params['b'] = params['b'].replace(kind=Parameter.POSITIONAL_ONLY)
+ foo.__signature__ = inspect.Signature(params.values())
+ sig = inspect.signature(foo)
+ self.assertEqual(str(sig), '(a, b, /, c, d, **kwargs)')
+
+ self.assertEqual(self.signature(partial(foo, 1)),
+ ((('b', ..., ..., 'positional_only'),
+ ('c', ..., ..., 'positional_or_keyword'),
+ ('d', ..., ..., 'positional_or_keyword'),
+ ('kwargs', ..., ..., 'var_keyword')),
+ ...))
+
+ self.assertEqual(self.signature(partial(foo, 1, 2)),
+ ((('c', ..., ..., 'positional_or_keyword'),
+ ('d', ..., ..., 'positional_or_keyword'),
+ ('kwargs', ..., ..., 'var_keyword')),
+ ...))
+
+ self.assertEqual(self.signature(partial(foo, 1, 2, 3)),
+ ((('d', ..., ..., 'positional_or_keyword'),
+ ('kwargs', ..., ..., 'var_keyword')),
+ ...))
+
+ self.assertEqual(self.signature(partial(foo, 1, 2, c=3)),
+ ((('c', 3, ..., 'keyword_only'),
+ ('d', ..., ..., 'keyword_only'),
+ ('kwargs', ..., ..., 'var_keyword')),
+ ...))
+
+ self.assertEqual(self.signature(partial(foo, 1, c=3)),
+ ((('b', ..., ..., 'positional_only'),
+ ('c', 3, ..., 'keyword_only'),
+ ('d', ..., ..., 'keyword_only'),
+ ('kwargs', ..., ..., 'var_keyword')),
+ ...))
+
+ def test_signature_on_partialmethod(self):
+ from functools import partialmethod
+
+ class Spam:
+ def test():
+ pass
+ ham = partialmethod(test)
+
+ with self.assertRaisesRegex(ValueError, "has incorrect arguments"):
+ inspect.signature(Spam.ham)
+
+ class Spam:
+ def test(it, a, *, c) -> 'spam':
+ pass
+ ham = partialmethod(test, c=1)
+
+ self.assertEqual(self.signature(Spam.ham),
+ ((('it', ..., ..., 'positional_or_keyword'),
+ ('a', ..., ..., 'positional_or_keyword'),
+ ('c', 1, ..., 'keyword_only')),
+ 'spam'))
+
+ self.assertEqual(self.signature(Spam().ham),
+ ((('a', ..., ..., 'positional_or_keyword'),
+ ('c', 1, ..., 'keyword_only')),
+ 'spam'))
+
+ def test_signature_on_fake_partialmethod(self):
+ def foo(a): pass
+ foo._partialmethod = 'spam'
+ self.assertEqual(str(inspect.signature(foo)), '(a)')
def test_signature_on_decorated(self):
import functools
@@ -1736,6 +2289,17 @@ class TestSignatureObject(unittest.TestCase):
((('b', ..., ..., "positional_or_keyword"),),
...))
+ # Test we handle __signature__ partway down the wrapper stack
+ def wrapped_foo_call():
+ pass
+ wrapped_foo_call.__wrapped__ = Foo.__call__
+
+ self.assertEqual(self.signature(wrapped_foo_call),
+ ((('a', ..., ..., "positional_or_keyword"),
+ ('b', ..., ..., "positional_or_keyword")),
+ ...))
+
+
def test_signature_on_class(self):
class C:
def __init__(self, a):
@@ -1817,6 +2381,49 @@ class TestSignatureObject(unittest.TestCase):
('bar', 2, ..., "keyword_only")),
...))
+ @unittest.skipIf(MISSING_C_DOCSTRINGS,
+ "Signature information for builtins requires docstrings")
+ def test_signature_on_class_without_init(self):
+ # Test classes without user-defined __init__ or __new__
+ class C: pass
+ self.assertEqual(str(inspect.signature(C)), '()')
+ class D(C): pass
+ self.assertEqual(str(inspect.signature(D)), '()')
+
+ # Test meta-classes without user-defined __init__ or __new__
+ class C(type): pass
+ class D(C): pass
+ with self.assertRaisesRegex(ValueError, "callable.*is not supported"):
+ self.assertEqual(inspect.signature(C), None)
+ with self.assertRaisesRegex(ValueError, "callable.*is not supported"):
+ self.assertEqual(inspect.signature(D), None)
+
+ @unittest.skipIf(MISSING_C_DOCSTRINGS,
+ "Signature information for builtins requires docstrings")
+ def test_signature_on_builtin_class(self):
+ self.assertEqual(str(inspect.signature(_pickle.Pickler)),
+ '(file, protocol=None, fix_imports=True)')
+
+ class P(_pickle.Pickler): pass
+ class EmptyTrait: pass
+ class P2(EmptyTrait, P): pass
+ self.assertEqual(str(inspect.signature(P)),
+ '(file, protocol=None, fix_imports=True)')
+ self.assertEqual(str(inspect.signature(P2)),
+ '(file, protocol=None, fix_imports=True)')
+
+ class P3(P2):
+ def __init__(self, spam):
+ pass
+ self.assertEqual(str(inspect.signature(P3)), '(spam)')
+
+ class MetaP(type):
+ def __call__(cls, foo, bar):
+ pass
+ class P4(P2, metaclass=MetaP):
+ pass
+ self.assertEqual(str(inspect.signature(P4)), '(foo, bar)')
+
def test_signature_on_callable_objects(self):
class Foo:
def __call__(self, a):
@@ -1838,18 +2445,16 @@ class TestSignatureObject(unittest.TestCase):
((('a', ..., ..., "positional_or_keyword"),),
...))
- class ToFail:
- __call__ = type
- with self.assertRaisesRegex(ValueError, "not supported by signature"):
- inspect.signature(ToFail())
-
-
class Wrapped:
pass
Wrapped.__wrapped__ = lambda a: None
self.assertEqual(self.signature(Wrapped),
((('a', ..., ..., "positional_or_keyword"),),
...))
+ # wrapper loop:
+ Wrapped.__wrapped__ = Wrapped
+ with self.assertRaisesRegex(ValueError, 'wrapper loop'):
+ self.signature(Wrapped)
def test_signature_on_lambdas(self):
self.assertEqual(self.signature((lambda a=10: a)),
@@ -1858,47 +2463,62 @@ class TestSignatureObject(unittest.TestCase):
def test_signature_equality(self):
def foo(a, *, b:int) -> float: pass
- self.assertNotEqual(inspect.signature(foo), 42)
+ self.assertFalse(inspect.signature(foo) == 42)
+ self.assertTrue(inspect.signature(foo) != 42)
+ self.assertTrue(inspect.signature(foo) == EqualsToAll())
+ self.assertFalse(inspect.signature(foo) != EqualsToAll())
def bar(a, *, b:int) -> float: pass
- self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) == inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) != inspect.signature(bar))
def bar(a, *, b:int) -> int: pass
- self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) == inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) != inspect.signature(bar))
def bar(a, *, b:int): pass
- self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) == inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) != inspect.signature(bar))
def bar(a, *, b:int=42) -> float: pass
- self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) == inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) != inspect.signature(bar))
def bar(a, *, c) -> float: pass
- self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) == inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) != inspect.signature(bar))
def bar(a, b:int) -> float: pass
- self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) == inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) != inspect.signature(bar))
def spam(b:int, a) -> float: pass
- self.assertNotEqual(inspect.signature(spam), inspect.signature(bar))
+ self.assertFalse(inspect.signature(spam) == inspect.signature(bar))
+ self.assertTrue(inspect.signature(spam) != inspect.signature(bar))
def foo(*, a, b, c): pass
def bar(*, c, b, a): pass
- self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) == inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) != inspect.signature(bar))
def foo(*, a=1, b, c): pass
def bar(*, c, b, a=1): pass
- self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) == inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) != inspect.signature(bar))
def foo(pos, *, a=1, b, c): pass
def bar(pos, *, c, b, a=1): pass
- self.assertEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) == inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) != inspect.signature(bar))
def foo(pos, *, a, b, c): pass
def bar(pos, *, c, b, a=1): pass
- self.assertNotEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) == inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) != 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.assertEqual(inspect.signature(foo), inspect.signature(bar))
+ self.assertTrue(inspect.signature(foo) == inspect.signature(bar))
+ self.assertFalse(inspect.signature(foo) != inspect.signature(bar))
def test_signature_unhashable(self):
def foo(a): pass
@@ -1923,6 +2543,7 @@ class TestSignatureObject(unittest.TestCase):
def test_signature_str_positional_only(self):
P = inspect.Parameter
+ S = inspect.Signature
def test(a_po, *, b, **kwargs):
return a_po, kwargs
@@ -1933,14 +2554,20 @@ class TestSignatureObject(unittest.TestCase):
test.__signature__ = sig.replace(parameters=new_params)
self.assertEqual(str(inspect.signature(test)),
- '(<a_po>, *, b, **kwargs)')
+ '(a_po, /, *, b, **kwargs)')
- sig = inspect.signature(test)
- new_params = list(sig.parameters.values())
- new_params[0] = new_params[0].replace(name=None)
- test.__signature__ = sig.replace(parameters=new_params)
- self.assertEqual(str(inspect.signature(test)),
- '(<0>, *, b, **kwargs)')
+ self.assertEqual(str(S(parameters=[P('foo', P.POSITIONAL_ONLY)])),
+ '(foo, /)')
+
+ self.assertEqual(str(S(parameters=[
+ P('foo', P.POSITIONAL_ONLY),
+ P('bar', P.VAR_KEYWORD)])),
+ '(foo, /, **bar)')
+
+ self.assertEqual(str(S(parameters=[
+ P('foo', P.POSITIONAL_ONLY),
+ P('bar', P.VAR_POSITIONAL)])),
+ '(foo, /, *bar)')
def test_signature_replace_anno(self):
def test() -> 42:
@@ -1955,6 +2582,22 @@ class TestSignatureObject(unittest.TestCase):
self.assertEqual(sig.return_annotation, 42)
self.assertEqual(sig, inspect.signature(test))
+ def test_signature_on_mangled_parameters(self):
+ class Spam:
+ def foo(self, __p1:1=2, *, __p2:2=3):
+ pass
+ class Ham(Spam):
+ pass
+
+ self.assertEqual(self.signature(Spam.foo),
+ ((('self', ..., ..., "positional_or_keyword"),
+ ('_Spam__p1', 2, 1, "positional_or_keyword"),
+ ('_Spam__p2', 3, 2, "keyword_only")),
+ ...))
+
+ self.assertEqual(self.signature(Spam.foo),
+ self.signature(Ham.foo))
+
class TestParameterObject(unittest.TestCase):
def test_signature_parameter_kinds(self):
@@ -1979,10 +2622,13 @@ class TestParameterObject(unittest.TestCase):
with self.assertRaisesRegex(ValueError, 'not a valid parameter name'):
inspect.Parameter('1', kind=inspect.Parameter.VAR_KEYWORD)
- with self.assertRaisesRegex(ValueError,
- 'non-positional-only parameter'):
+ with self.assertRaisesRegex(TypeError, 'name must be a str'):
inspect.Parameter(None, kind=inspect.Parameter.VAR_KEYWORD)
+ with self.assertRaisesRegex(ValueError,
+ 'is not a valid parameter name'):
+ inspect.Parameter('$', kind=inspect.Parameter.VAR_KEYWORD)
+
with self.assertRaisesRegex(ValueError, 'cannot have default values'):
inspect.Parameter('a', default=42,
kind=inspect.Parameter.VAR_KEYWORD)
@@ -2002,11 +2648,17 @@ class TestParameterObject(unittest.TestCase):
P = inspect.Parameter
p = P('foo', default=42, kind=inspect.Parameter.KEYWORD_ONLY)
- self.assertEqual(p, p)
- self.assertNotEqual(p, 42)
+ self.assertTrue(p == p)
+ self.assertFalse(p != p)
+ self.assertFalse(p == 42)
+ self.assertTrue(p != 42)
+ self.assertTrue(p == EqualsToAll())
+ self.assertFalse(p != EqualsToAll())
- self.assertEqual(p, P('foo', default=42,
- kind=inspect.Parameter.KEYWORD_ONLY))
+ self.assertTrue(p == P('foo', default=42,
+ kind=inspect.Parameter.KEYWORD_ONLY))
+ self.assertFalse(p != P('foo', default=42,
+ kind=inspect.Parameter.KEYWORD_ONLY))
def test_signature_parameter_unhashable(self):
p = inspect.Parameter('foo', default=42,
@@ -2031,7 +2683,8 @@ class TestParameterObject(unittest.TestCase):
self.assertEqual(p2.name, 'bar')
self.assertNotEqual(p2, p)
- with self.assertRaisesRegex(ValueError, 'not a valid parameter name'):
+ with self.assertRaisesRegex(ValueError,
+ 'name is a required attribute'):
p2 = p2.replace(name=p2.empty)
p2 = p2.replace(name='foo', default=None)
@@ -2053,14 +2706,11 @@ class TestParameterObject(unittest.TestCase):
self.assertEqual(p2, p)
def test_signature_parameter_positional_only(self):
- p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
- self.assertEqual(str(p), '<>')
-
- p = p.replace(name='1')
- self.assertEqual(str(p), '<1>')
+ with self.assertRaisesRegex(TypeError, 'name must be a str'):
+ inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
def test_signature_parameter_immutability(self):
- p = inspect.Parameter(None, kind=inspect.Parameter.POSITIONAL_ONLY)
+ p = inspect.Parameter('spam', kind=inspect.Parameter.KEYWORD_ONLY)
with self.assertRaises(AttributeError):
p.foo = 'bar'
@@ -2258,6 +2908,15 @@ class TestSignatureBind(unittest.TestCase):
self.assertEqual(self.call(test, 1, 2, 4, 5, bar=6),
(1, 2, 4, 5, 6, {}))
+ self.assertEqual(self.call(test, 1, 2),
+ (1, 2, 3, 42, 50, {}))
+
+ self.assertEqual(self.call(test, 1, 2, foo=4, bar=5),
+ (1, 2, 3, 4, 5, {}))
+
+ with self.assertRaisesRegex(TypeError, "but was passed as a keyword"):
+ self.call(test, 1, 2, foo=4, bar=5, c_po=10)
+
with self.assertRaisesRegex(TypeError, "parameter is positional only"):
self.call(test, 1, 2, c_po=4)
@@ -2274,6 +2933,22 @@ class TestSignatureBind(unittest.TestCase):
ba = sig.bind(1, self=2, b=3)
self.assertEqual(ba.args, (1, 2, 3))
+ def test_signature_bind_vararg_name(self):
+ def test(a, *args):
+ return a, args
+ sig = inspect.signature(test)
+
+ with self.assertRaisesRegex(TypeError, "too many keyword arguments"):
+ sig.bind(a=0, args=1)
+
+ def test(*args, **kwargs):
+ return args, kwargs
+ self.assertEqual(self.call(test, args=1), ((), {'args': 1}))
+
+ sig = inspect.signature(test)
+ ba = sig.bind(args=1)
+ self.assertEqual(ba.arguments, {'kwargs': {'args': 1}})
+
class TestBoundArguments(unittest.TestCase):
def test_signature_bound_arguments_unhashable(self):
@@ -2286,19 +2961,223 @@ class TestBoundArguments(unittest.TestCase):
def test_signature_bound_arguments_equality(self):
def foo(a): pass
ba = inspect.signature(foo).bind(1)
- self.assertEqual(ba, ba)
+ self.assertTrue(ba == ba)
+ self.assertFalse(ba != ba)
+ self.assertTrue(ba == EqualsToAll())
+ self.assertFalse(ba != EqualsToAll())
ba2 = inspect.signature(foo).bind(1)
- self.assertEqual(ba, ba2)
+ self.assertTrue(ba == ba2)
+ self.assertFalse(ba != ba2)
ba3 = inspect.signature(foo).bind(2)
- self.assertNotEqual(ba, ba3)
+ self.assertFalse(ba == ba3)
+ self.assertTrue(ba != ba3)
ba3.arguments['a'] = 1
- self.assertEqual(ba, ba3)
+ self.assertTrue(ba == ba3)
+ self.assertFalse(ba != ba3)
def bar(b): pass
ba4 = inspect.signature(bar).bind(1)
- self.assertNotEqual(ba, ba4)
+ self.assertFalse(ba == ba4)
+ self.assertTrue(ba != ba4)
+
+
+class TestSignaturePrivateHelpers(unittest.TestCase):
+ def test_signature_get_bound_param(self):
+ getter = inspect._signature_get_bound_param
+
+ self.assertEqual(getter('($self)'), 'self')
+ self.assertEqual(getter('($self, obj)'), 'self')
+ self.assertEqual(getter('($cls, /, obj)'), 'cls')
+
+ def _strip_non_python_syntax(self, input,
+ clean_signature, self_parameter, last_positional_only):
+ computed_clean_signature, \
+ computed_self_parameter, \
+ computed_last_positional_only = \
+ inspect._signature_strip_non_python_syntax(input)
+ self.assertEqual(computed_clean_signature, clean_signature)
+ self.assertEqual(computed_self_parameter, self_parameter)
+ self.assertEqual(computed_last_positional_only, last_positional_only)
+
+ def test_signature_strip_non_python_syntax(self):
+ self._strip_non_python_syntax(
+ "($module, /, path, mode, *, dir_fd=None, " +
+ "effective_ids=False,\n follow_symlinks=True)",
+ "(module, path, mode, *, dir_fd=None, " +
+ "effective_ids=False, follow_symlinks=True)",
+ 0,
+ 0)
+
+ self._strip_non_python_syntax(
+ "($module, word, salt, /)",
+ "(module, word, salt)",
+ 0,
+ 2)
+
+ self._strip_non_python_syntax(
+ "(x, y=None, z=None, /)",
+ "(x, y=None, z=None)",
+ None,
+ 2)
+
+ self._strip_non_python_syntax(
+ "(x, y=None, z=None)",
+ "(x, y=None, z=None)",
+ None,
+ None)
+
+ self._strip_non_python_syntax(
+ "(x,\n y=None,\n z = None )",
+ "(x, y=None, z=None)",
+ None,
+ None)
+
+ self._strip_non_python_syntax(
+ "",
+ "",
+ None,
+ None)
+
+ self._strip_non_python_syntax(
+ None,
+ None,
+ None,
+ None)
+
+
+class TestUnwrap(unittest.TestCase):
+
+ def test_unwrap_one(self):
+ def func(a, b):
+ return a + b
+ wrapper = functools.lru_cache(maxsize=20)(func)
+ self.assertIs(inspect.unwrap(wrapper), func)
+
+ def test_unwrap_several(self):
+ def func(a, b):
+ return a + b
+ wrapper = func
+ for __ in range(10):
+ @functools.wraps(wrapper)
+ def wrapper():
+ pass
+ self.assertIsNot(wrapper.__wrapped__, func)
+ self.assertIs(inspect.unwrap(wrapper), func)
+
+ def test_stop(self):
+ def func1(a, b):
+ return a + b
+ @functools.wraps(func1)
+ def func2():
+ pass
+ @functools.wraps(func2)
+ def wrapper():
+ pass
+ func2.stop_here = 1
+ unwrapped = inspect.unwrap(wrapper,
+ stop=(lambda f: hasattr(f, "stop_here")))
+ self.assertIs(unwrapped, func2)
+
+ def test_cycle(self):
+ def func1(): pass
+ func1.__wrapped__ = func1
+ with self.assertRaisesRegex(ValueError, 'wrapper loop'):
+ inspect.unwrap(func1)
+
+ def func2(): pass
+ func2.__wrapped__ = func1
+ func1.__wrapped__ = func2
+ with self.assertRaisesRegex(ValueError, 'wrapper loop'):
+ inspect.unwrap(func1)
+ with self.assertRaisesRegex(ValueError, 'wrapper loop'):
+ inspect.unwrap(func2)
+
+ def test_unhashable(self):
+ def func(): pass
+ func.__wrapped__ = None
+ class C:
+ __hash__ = None
+ __wrapped__ = func
+ self.assertIsNone(inspect.unwrap(C()))
+
+class TestMain(unittest.TestCase):
+ def test_only_source(self):
+ module = importlib.import_module('unittest')
+ rc, out, err = assert_python_ok('-m', 'inspect',
+ 'unittest')
+ lines = out.decode().splitlines()
+ # ignore the final newline
+ self.assertEqual(lines[:-1], inspect.getsource(module).splitlines())
+ self.assertEqual(err, b'')
+
+ def test_custom_getattr(self):
+ def foo():
+ pass
+ foo.__signature__ = 42
+ with self.assertRaises(TypeError):
+ inspect.signature(foo)
+
+ @unittest.skipIf(ThreadPoolExecutor is None,
+ 'threads required to test __qualname__ for source files')
+ def test_qualname_source(self):
+ rc, out, err = assert_python_ok('-m', 'inspect',
+ 'concurrent.futures:ThreadPoolExecutor')
+ lines = out.decode().splitlines()
+ # ignore the final newline
+ self.assertEqual(lines[:-1],
+ inspect.getsource(ThreadPoolExecutor).splitlines())
+ self.assertEqual(err, b'')
+
+ def test_builtins(self):
+ module = importlib.import_module('unittest')
+ _, out, err = assert_python_failure('-m', 'inspect',
+ 'sys')
+ lines = err.decode().splitlines()
+ self.assertEqual(lines, ["Can't get info for builtin modules."])
+
+ def test_details(self):
+ module = importlib.import_module('unittest')
+ rc, out, err = assert_python_ok('-m', 'inspect',
+ 'unittest', '--details')
+ output = out.decode()
+ # Just a quick sanity check on the output
+ self.assertIn(module.__name__, output)
+ self.assertIn(module.__file__, output)
+ if not sys.flags.optimize:
+ self.assertIn(module.__cached__, output)
+ self.assertEqual(err, b'')
+
+
+class TestReload(unittest.TestCase):
+
+ src_before = textwrap.dedent("""\
+def foo():
+ print("Bla")
+ """)
+
+ src_after = textwrap.dedent("""\
+def foo():
+ print("Oh no!")
+ """)
+
+ def assertInspectEqual(self, path, source):
+ inspected_src = inspect.getsource(source)
+ with open(path) as src:
+ self.assertEqual(
+ src.read().splitlines(True),
+ inspected_src.splitlines(True)
+ )
+
+ def test_getsource_reload(self):
+ # see issue 1218234
+ with _ready_to_import('reload_bug', self.src_before) as (name, path):
+ module = importlib.import_module(name)
+ self.assertInspectEqual(path, module)
+ with open(path, 'w') as src:
+ src.write(self.src_after)
+ self.assertInspectEqual(path, module)
def test_main():
@@ -2308,7 +3187,8 @@ def test_main():
TestGetcallargsFunctions, TestGetcallargsMethods,
TestGetcallargsUnboundMethods, TestGetattrStatic, TestGetGeneratorState,
TestNoEOL, TestSignatureObject, TestSignatureBind, TestParameterObject,
- TestBoundArguments, TestGetClosureVars
+ TestBoundArguments, TestSignaturePrivateHelpers, TestGetClosureVars,
+ TestUnwrap, TestMain, TestReload
)
if __name__ == "__main__":