From d53cf99dca4605ace4b81b1e585616b3e1b74fa6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 6 May 2019 22:40:27 +0300 Subject: [PATCH] bpo-36542: Allow to overwrite the signature for Python functions. (GH-12705) --- Lib/bdb.py | 1 + Lib/cProfile.py | 1 + Lib/collections/__init__.py | 2 ++ Lib/concurrent/futures/_base.py | 1 + Lib/concurrent/futures/process.py | 1 + Lib/concurrent/futures/thread.py | 1 + Lib/contextlib.py | 2 ++ Lib/curses/__init__.py | 1 + Lib/functools.py | 1 + Lib/inspect.py | 9 +++++++-- Lib/multiprocessing/managers.py | 2 ++ Lib/profile.py | 1 + Lib/test/test_inspect.py | 11 +++++++++++ Lib/trace.py | 1 + Lib/unittest/case.py | 5 ++++- Lib/weakref.py | 1 + .../Library/2019-04-06-12-36-09.bpo-36542.Q0qyYV.rst | 2 ++ 17 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2019-04-06-12-36-09.bpo-36542.Q0qyYV.rst diff --git a/Lib/bdb.py b/Lib/bdb.py index 54aa9843745..69174364c46 100644 --- a/Lib/bdb.py +++ b/Lib/bdb.py @@ -649,6 +649,7 @@ class Bdb: self.quitting = True sys.settrace(None) return res + runcall.__text_signature__ = '($self, func, /, *args, **kwds)' def set_trace(): diff --git a/Lib/cProfile.py b/Lib/cProfile.py index 2e449cc576c..369d02e22e2 100755 --- a/Lib/cProfile.py +++ b/Lib/cProfile.py @@ -124,6 +124,7 @@ class Profile(_lsprof.Profiler): return func(*args, **kw) finally: self.disable() + runcall.__text_signature__ = '($self, func, /, *args, **kw)' def __enter__(self): self.enable() diff --git a/Lib/collections/__init__.py b/Lib/collections/__init__.py index 9657c1cf83b..e6cafb320fa 100644 --- a/Lib/collections/__init__.py +++ b/Lib/collections/__init__.py @@ -1018,6 +1018,8 @@ class UserDict(_collections_abc.MutableMapping): self.update(dict) if kwargs: self.update(kwargs) + __init__.__text_signature__ = '($self, dict=None, /, **kwargs)' + def __len__(self): return len(self.data) def __getitem__(self, key): if key in self.data: diff --git a/Lib/concurrent/futures/_base.py b/Lib/concurrent/futures/_base.py index ea16eef841c..8f155f0ea82 100644 --- a/Lib/concurrent/futures/_base.py +++ b/Lib/concurrent/futures/_base.py @@ -567,6 +567,7 @@ class Executor(object): 'got %d' % (len(args)-1)) raise NotImplementedError() + submit.__text_signature__ = '($self, fn, /, *args, **kwargs)' def map(self, fn, *iterables, timeout=None, chunksize=1): """Returns an iterator equivalent to map(fn, iter). diff --git a/Lib/concurrent/futures/process.py b/Lib/concurrent/futures/process.py index e6ce278b5d4..21bf4a447f0 100644 --- a/Lib/concurrent/futures/process.py +++ b/Lib/concurrent/futures/process.py @@ -630,6 +630,7 @@ class ProcessPoolExecutor(_base.Executor): self._start_queue_management_thread() return f + submit.__text_signature__ = _base.Executor.submit.__text_signature__ submit.__doc__ = _base.Executor.submit.__doc__ def map(self, fn, *iterables, timeout=None, chunksize=1): diff --git a/Lib/concurrent/futures/thread.py b/Lib/concurrent/futures/thread.py index 0a61e3a9ac1..2af31a106dd 100644 --- a/Lib/concurrent/futures/thread.py +++ b/Lib/concurrent/futures/thread.py @@ -174,6 +174,7 @@ class ThreadPoolExecutor(_base.Executor): self._work_queue.put(w) self._adjust_thread_count() return f + submit.__text_signature__ = _base.Executor.submit.__text_signature__ submit.__doc__ = _base.Executor.submit.__doc__ def _adjust_thread_count(self): diff --git a/Lib/contextlib.py b/Lib/contextlib.py index ae498a2b6ef..de989a001c6 100644 --- a/Lib/contextlib.py +++ b/Lib/contextlib.py @@ -454,6 +454,7 @@ class _BaseExitStack: _exit_wrapper.__wrapped__ = callback self._push_exit_callback(_exit_wrapper) return callback # Allow use as a decorator + callback.__text_signature__ = '($self, callback, /, *args, **kwds)' def _push_cm_exit(self, cm, cm_exit): """Helper to correctly register callbacks to __exit__ methods.""" @@ -615,6 +616,7 @@ class AsyncExitStack(_BaseExitStack, AbstractAsyncContextManager): _exit_wrapper.__wrapped__ = callback self._push_exit_callback(_exit_wrapper, False) return callback # Allow use as a decorator + push_async_callback.__text_signature__ = '($self, callback, /, *args, **kwds)' async def aclose(self): """Immediately unwind the context stack.""" diff --git a/Lib/curses/__init__.py b/Lib/curses/__init__.py index 44a19842882..24ff3ca93a8 100644 --- a/Lib/curses/__init__.py +++ b/Lib/curses/__init__.py @@ -110,3 +110,4 @@ def wrapper(*args, **kwds): echo() nocbreak() endwin() +wrapper.__text_signature__ = '(func, /, *args, **kwds)' diff --git a/Lib/functools.py b/Lib/functools.py index 1f1874db9b4..28d9f6f75fd 100644 --- a/Lib/functools.py +++ b/Lib/functools.py @@ -388,6 +388,7 @@ class partialmethod(object): self.func = func self.args = args self.keywords = keywords + __init__.__text_signature__ = '($self, func, /, *args, **keywords)' def __repr__(self): args = ", ".join(map(repr, self.args)) diff --git a/Lib/inspect.py b/Lib/inspect.py index c460309bb5a..6c3027987b3 100644 --- a/Lib/inspect.py +++ b/Lib/inspect.py @@ -2121,7 +2121,7 @@ def _signature_from_builtin(cls, func, skip_bound_arg=True): return _signature_fromstr(cls, func, s, skip_bound_arg) -def _signature_from_function(cls, func): +def _signature_from_function(cls, func, skip_bound_arg=True): """Private helper: constructs Signature for the given python function.""" is_duck_function = False @@ -2133,6 +2133,10 @@ def _signature_from_function(cls, func): # of pure function: raise TypeError('{!r} is not a Python function'.format(func)) + s = getattr(func, "__text_signature__", None) + if s: + return _signature_fromstr(cls, func, s, skip_bound_arg) + Parameter = cls._parameter_cls # Parameter information. @@ -2301,7 +2305,8 @@ def _signature_from_callable(obj, *, if isfunction(obj) or _signature_is_functionlike(obj): # If it's a pure Python function, or an object that is duck type # of a Python function (Cython functions, for instance), then: - return _signature_from_function(sigcls, obj) + return _signature_from_function(sigcls, obj, + skip_bound_arg=skip_bound_arg) if _signature_is_builtin(obj): return _signature_from_builtin(sigcls, obj, diff --git a/Lib/multiprocessing/managers.py b/Lib/multiprocessing/managers.py index 80c3ddb9154..22abd47fb1f 100644 --- a/Lib/multiprocessing/managers.py +++ b/Lib/multiprocessing/managers.py @@ -419,6 +419,7 @@ class Server(object): self.incref(c, ident) return ident, tuple(exposed) + create.__text_signature__ = '($self, c, typeid, /, *args, **kwds)' def get_methods(self, c, token): ''' @@ -1309,6 +1310,7 @@ if HAS_SHMEM: if hasattr(self.registry[typeid][-1], "_shared_memory_proxy"): kwargs['shared_memory_context'] = self.shared_memory_context return Server.create(*args, **kwargs) + create.__text_signature__ = '($self, c, typeid, /, *args, **kwargs)' def shutdown(self, c): "Call unlink() on all tracked shared memory, terminate the Server." diff --git a/Lib/profile.py b/Lib/profile.py index 9a865d3f6f6..1346297c04a 100755 --- a/Lib/profile.py +++ b/Lib/profile.py @@ -447,6 +447,7 @@ class Profile: return func(*args, **kw) finally: sys.setprofile(None) + runcall.__text_signature__ = '($self, func, /, *args, **kw)' #****************************************************************** diff --git a/Lib/test/test_inspect.py b/Lib/test/test_inspect.py index 3c825b00e5e..c54cdb23c24 100644 --- a/Lib/test/test_inspect.py +++ b/Lib/test/test_inspect.py @@ -3782,6 +3782,17 @@ class TestSignatureDefinitions(unittest.TestCase): with self.subTest(builtin=name): self.assertIsNone(obj.__text_signature__) + def test_python_function_override_signature(self): + def func(*args, **kwargs): + pass + func.__text_signature__ = '($self, a, b=1, *args, c, d=2, **kwargs)' + sig = inspect.signature(func) + self.assertIsNotNone(sig) + self.assertEqual(str(sig), '(self, /, a, b=1, *args, c, d=2, **kwargs)') + func.__text_signature__ = '($self, a, b=1, /, *args, c, d=2, **kwargs)' + sig = inspect.signature(func) + self.assertEqual(str(sig), '(self, a, b=1, /, *args, c, d=2, **kwargs)') + class NTimesUnwrappable: def __init__(self, n): diff --git a/Lib/trace.py b/Lib/trace.py index fd40fbae850..63008a134a8 100755 --- a/Lib/trace.py +++ b/Lib/trace.py @@ -476,6 +476,7 @@ class Trace: if not self.donothing: sys.settrace(None) return result + runfunc.__text_signature__ = '($self, func, /, *args, **kw)' def file_module_function_of(self, frame): code = frame.f_code diff --git a/Lib/unittest/case.py b/Lib/unittest/case.py index 8ff2546fc20..8e01c3dc7bb 100644 --- a/Lib/unittest/case.py +++ b/Lib/unittest/case.py @@ -102,6 +102,7 @@ def addModuleCleanup(*args, **kwargs): args = tuple(args) _module_cleanups.append((function, args, kwargs)) +addModuleCleanup.__text_signature__ = '(function, /, *args, **kwargs)' def doModuleCleanups(): @@ -498,8 +499,8 @@ class TestCase(object): args = tuple(args) self._cleanups.append((function, args, kwargs)) + addCleanup.__text_signature__ = '($self, function, /, *args, **kwargs)' - @classmethod def addClassCleanup(*args, **kwargs): """Same as addCleanup, except the cleanup items are called even if setUpClass fails (unlike tearDownClass).""" @@ -514,6 +515,8 @@ class TestCase(object): args = tuple(args) cls._class_cleanups.append((function, args, kwargs)) + addClassCleanup.__text_signature__ = '($cls, function, /, *args, **kwargs)' + addClassCleanup = classmethod(addClassCleanup) def setUp(self): "Hook method for setting up the test fixture before exercising it." diff --git a/Lib/weakref.py b/Lib/weakref.py index 285c70792e0..1eeb7b0a0b4 100644 --- a/Lib/weakref.py +++ b/Lib/weakref.py @@ -569,6 +569,7 @@ class finalize: info.index = next(self._index_iter) self._registry[self] = info finalize._dirty = True + __init__.__text_signature__ = '($self, obj, func, /, *args, **kwargs)' def __call__(self, _=None): """If alive then mark as dead and return func(*args, **kwargs); diff --git a/Misc/NEWS.d/next/Library/2019-04-06-12-36-09.bpo-36542.Q0qyYV.rst b/Misc/NEWS.d/next/Library/2019-04-06-12-36-09.bpo-36542.Q0qyYV.rst new file mode 100644 index 00000000000..8374776e61e --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-04-06-12-36-09.bpo-36542.Q0qyYV.rst @@ -0,0 +1,2 @@ +The signature of Python functions can now be overridden by specifying the +``__text_signature__`` attribute.