Preserve default arguments of wrapped/decorated Python function in Sphinx documentation
I came up with a monkey-patch for functools.wraps
.
Accordingly, I simply added this to the conf.py
script in my project documentation's sphinx 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 no_op_wraps
that does absolutely nothing but simply return the original function.
You ordinarily can't. That is because the variable names used as parameters in the wrapped function are not even present on the wrapped function - so Sphinx do not know about them.
That is a known complicated issue in Python - so much that recent versions - including not only Python 3, but also Python 2.7 included a __wrapped__
attribute on class decorated that make the proper use from functools.wraps
-
that way, upon inspecting the decorated function one is able to know about the actual wrrapped function by looking at __wrapped__
. Unfortunatelly, Sphinxs ignores the __wrapped__
, and show the info on the wrapper function instead.
SO, one thing to do is certainly to report this as a bug to the Sphinx project itself - it should take __wrapped__
in account.
A meantime workaround for that would be to change the wrapper function to actually include more information about the wrapped - like its signature - so you could write another function to be called in place of "functools.wraps" for your project, which does just that: pre-pend the function signature to its docstring, if any. Unfortunatelly, retrieving the function signatures in Python older than 3.3 is tricky - (for 3.3 and newer, check https://docs.python.org/3/library/inspect.html#inspect-signature-object ) - but anyway, for a naive form, you could write another version of "wraps" along:
def wraps(original_func):
wrap_decorator = functools.wraps(original_func)
def re_wrapper(func):
wrapper = wrap_decorator(func)
poorman_sig = original_func.__code__.co_varnames[
:original_func.__code__.co_argcount]
wrapper.__doc__ = "{} ({})\n\n{}".format (
original_func.__name__, ", ".join(poorman_sig),
wrapper.__doc__)
return wrapper
return re_wrapper
And use that instead of "functools.wraps". It would at least add a line with the parameter names, (but not th e defalt values) as first line in the docs.
---Hmm..maybe it would be easier just to patch Sphinx to use __wrapped__
before getting this done right.