From 409d8a95ec3d6a7cd1f3a6ad915bc91817d56872 Mon Sep 17 00:00:00 2001 From: Dieter Maurer Date: Tue, 20 Jun 2023 08:26:39 +0200 Subject: [PATCH] Make `mapply` `__signature__` aware (#1135) * Make `mapply` `__signature__` aware * Update src/ZPublisher/mapply.py Co-authored-by: Gil Forcada Codinachs * Fix rest syntax (as suggested by review) Co-authored-by: Michael Howitz --------- Co-authored-by: Gil Forcada Codinachs Co-authored-by: Michael Howitz --- CHANGES.rst | 9 +++++++++ src/ZPublisher/mapply.py | 19 ++++++++++++++++--- src/ZPublisher/tests/test_mapply.py | 14 ++++++++++++++ 3 files changed, 39 insertions(+), 3 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 5afe97abcb..e99b7b40fe 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -20,6 +20,15 @@ https://github.com/zopefoundation/Zope/blob/4.x/CHANGES.rst - Update to newest compatible versions of dependencies. +- Make ``mapply`` ``__signature__`` aware. + This allows to publish methods decorated via a decorator + which sets ``__signature__`` on the wrapper to specify + the signature to use. + For details, see + `#1134 `_. + Note: ``mapply`` still does not support keyword only, var positional + and var keyword parameters. + 5.8.3 (2023-06-15) ------------------ diff --git a/src/ZPublisher/mapply.py b/src/ZPublisher/mapply.py index 19deeacf4d..d2259adcee 100644 --- a/src/ZPublisher/mapply.py +++ b/src/ZPublisher/mapply.py @@ -12,6 +12,8 @@ ############################################################################## """Provide an apply-like facility that works with any mapping object """ +from inspect import getfullargspec + import zope.publisher.publish @@ -50,9 +52,20 @@ def mapply(object, positional=(), keyword={}, if maybe: return object raise - code = f.__code__ - defaults = f.__defaults__ - names = code.co_varnames[count:code.co_argcount] + if hasattr(f, "__signature__"): + # The function has specified the signature to use + # (likely via a decorator) + # We use ``getfullargspec`` because it packages + # the signature information in the way we need it here. + # Should the function get deprecated, we could do the + # packaging ourselves + argspec = getfullargspec(f) + defaults = argspec.defaults + names = argspec.args[count:] + else: + code = f.__code__ + defaults = f.__defaults__ + names = code.co_varnames[count:code.co_argcount] nargs = len(names) if positional: diff --git a/src/ZPublisher/tests/test_mapply.py b/src/ZPublisher/tests/test_mapply.py index d0cc4eee3c..590df65db5 100644 --- a/src/ZPublisher/tests/test_mapply.py +++ b/src/ZPublisher/tests/test_mapply.py @@ -90,3 +90,17 @@ class NoCallButAcquisition(Acquisition.Implicit): ob = NoCallButAcquisition().__of__(Root()) self.assertRaises(TypeError, mapply, ob, (), {}) + + def testFunctionWithSignature(self): + from inspect import Parameter + from inspect import Signature + + def f(*args, **kw): + return args, kw + + f.__signature__ = Signature( + (Parameter("a", Parameter.POSITIONAL_OR_KEYWORD), + Parameter("b", Parameter.POSITIONAL_OR_KEYWORD, default="b"))) + + self.assertEqual(mapply(f, ("a",), {}), (("a", "b"), {})) + self.assertEqual(mapply(f, (), {"a": "A", "b": "B"}), (("A", "B"), {}))