Can you enhance Sphinx to replace *args and **kwargs with the real signature in the documentation of decorated functions?
Let's say I have the following decorator and decorated function:
import functools
def mywrapper(func):
@functools.wraps(func)
def new_func(*args, **kwargs):
print('Wrapping Ho!')
return func(*args, **kwargs)
return new_func
@mywrapper
def myfunc(foo=42, bar=43):
"""Obscure Addition
:param foo: bar!
:param bar: bla bla
:return: foo + bar
"""
return foo + bar
Accordingly, calling print(myfunc(3, 4)) gives us:
Wrapping Ho!
7
So far so good. I also want my library containing myfunc properly documented with Sphinx.
However, if I include my function in my sphinx html page via:
.. automodule:: mymodule
:members: myfunc
It will actually show up as:
myfunc(*args, **kwargs)Obscure Addition
It would be nice if the generic myfunc(*args, **kwargs) in the tile would be replaced by myfunc(foo=42, bar=43).
I cam up with a work-around by simply monkey-patching functools.wraps.
I simply added this to the conf.py script in the sphinx documentation source folder:
# Monkey-patch functools.wraps
import functools
def no_op_wraps(func):
"""Replaces functools.wraps in order to undo wrapping.
Can be used to preserve the decorated function's signature
in the documentation generated by Sphinx.
"""
def wrapper(decorator):
return func
return wrapper
functools.wraps = no_op_wraps
Hence, when building the html page via make html, functools.wraps is replaced with this decorator that does absolutely nothing but simply return the original function.
Also discussed on Stackoverflow.
It seems that importing matplotlib in a module will give errors when functools.wraps is replaced by a dummy. To work around this you can test if the function to be wrapped is inside the package you are documenting. Put the following in the conf.py file:
# Monkey-patch functools.wraps
# See: https://github.com/sphinx-doc/sphinx/issues/1711
import functools
def no_op_wraps(func):
"""
Replaces functools.wraps in order to undo wrapping when generating Sphinx documentation
"""
if func.__module__ is None or 'MY_PACKAGE_NAME' not in func.__module__:
return functools.orig_wraps(func)
def wrapper(decorator):
return func
return wrapper
functools.orig_wraps = functools.wraps
functools.wraps = no_op_wraps
This works for me at least
Following the given monkey patch with:
import contextlib
contextlib.wraps = no_op_wraps
works for functions and methods decorated by @contextlib.contextmanager
@SmokinCaterpillar — Is there a special place in conf.py where the patch goes? Simply placing it in the file has no effect for me.
Does anyone know whether it would be possible to just patch Sphinx to use __wrapped__? I have no idea how one might do that!
@orome you can see a version of my conf.py workaround here if it helps, https://github.com/jquast/blessed/blob/master/docs/conf.py#L38-L55
Take note of the conditional expression, or 'blessed' not in func.__module__ -- this ensures patching is contained to the blessed module
Here is an example of a custom decorator to solve the same issue:
def required(*req_args):
def wrapper(f):
def wrap(self, *args, **kwargs):
return f(self, *args, **kwargs)
return wrap
return wrapper
@required('something')
def my_thing(self, *args, **kwargs):
pass
import my_project
def no_wrap(*args)
def wrap(f):
return f
return wrap
my_project.required = no_wrap
It seems the latest Sphinx (v3.1.2) uses the signature of original function. On my local, the reported code is rendered as example.myfunc(foo=42, bar=43). I don't know which release fixes this.
Anyway, this issue was already resolved. Now I'm closing this.
Please file a new issue if you still have the same error.