UPDATE: this may be "impossible" to do cleanly because sphinx uses the function's code object to generate its function signature. But, since you're using sphinx, there is a hacky workaround that does works.
It's hacky because it effectively disables the decorator while sphinx is running, but it does work, so it's a practical solution.
At first I went down the route of constructing a new types.CodeType
object, to replace the wrapper's func_code
code object member, which is what sphinx uses when generating the signatures.
I was able to segfault python by going down the route or trying to swap in the co_varnames
, co_nlocals
, etc. members of the code object from the original function, and while appealing, it was too complicated.
The following solution, while it is a hacky heavy hammer, is also very simple =)
The approach is as follows: when running inside sphinx, set an environment variable that the decorator can check. inside the decorator, when sphinx is detected, don't do any decorating at all, and instead return the original function.
Inside your sphinx conf.py:
import os
os.environ['SPHINX_BUILD'] = '1'
And then here is an example module with a test case that shows what it might look like:
import functools
import os
import types
import unittest
SPHINX_BUILD = bool(os.environ.get('SPHINX_BUILD', ''))
class StaleError(StandardError):
"""Custom exception for staleness"""
pass
def check_stale(f):
"""Raise StaleError when the object has gone stale"""
if SPHINX_BUILD:
# sphinx hack: use the original function when sphinx is running so that the
# documentation ends up with the correct function signatures.
# See 'SPHINX_BUILD' in conf.py.
return f
@functools.wraps(f)
def wrapper(self, *args, **kwargs):
if self.stale:
raise StaleError('stale')
return f(self, *args, **kwargs)
return wrapper
class Example(object):
def __init__(self):
self.stale = False
self.value = 0
@check_stale
def get(self):
"""docstring"""
return self.value
@check_stale
def calculate(self, a, b, c):
"""docstring"""
return self.value + a + b + c
class TestCase(unittest.TestCase):
def test_example(self):
example = Example()
self.assertEqual(example.get(), 0)
example.value = 1
example.stale = True
self.assertRaises(StaleError, example.get)
example.stale = False
self.assertEqual(example.calculate(1, 1, 1), 4)
if __name__ == '__main__':
unittest.main()