Skip to content

Commit

Permalink
Make mapply __signature__ aware (#1135)
Browse files Browse the repository at this point in the history
* Make `mapply` `__signature__` aware

* Update src/ZPublisher/mapply.py

Co-authored-by: Gil Forcada Codinachs <gil.gnome@gmail.com>

* Fix rest syntax (as suggested by review)

Co-authored-by: Michael Howitz <mh@gocept.com>

---------

Co-authored-by: Gil Forcada Codinachs <gil.gnome@gmail.com>
Co-authored-by: Michael Howitz <mh@gocept.com>
  • Loading branch information
3 people committed Jun 20, 2023
1 parent b0bb108 commit 409d8a9
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 3 deletions.
9 changes: 9 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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 <https://github.com/zopefoundation/Zope/issues/1134>`_.
Note: ``mapply`` still does not support keyword only, var positional
and var keyword parameters.


5.8.3 (2023-06-15)
------------------
Expand Down
19 changes: 16 additions & 3 deletions src/ZPublisher/mapply.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
##############################################################################
"""Provide an apply-like facility that works with any mapping object
"""
from inspect import getfullargspec

import zope.publisher.publish


Expand Down Expand Up @@ -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:
Expand Down
14 changes: 14 additions & 0 deletions src/ZPublisher/tests/test_mapply.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"), {}))

0 comments on commit 409d8a9

Please sign in to comment.