def docstr_applier(func): #docstr = meta_util_six.get_funcdoc(docstr_func) #meta_util_six.set_funcdoc(func, docstr) if isinstance(docstr_func, six.string_types): olddoc = meta_util_six.get_funcdoc(func) if olddoc is None: olddoc = '' newdoc = olddoc + docstr_func meta_util_six.set_funcdoc(func, newdoc) return func else: preserved_func = preserve_sig(func, docstr_func) return preserved_func
def make_redirect(func): # PRESERVES ALL SIGNATURES WITH EXECS src_fmt = r''' def {funcname}{defsig}: """ {orig_docstr}""" return {orig_funcname}{callsig} ''' from utool._internal import meta_util_six orig_docstr = meta_util_six.get_funcdoc(func) funcname = meta_util_six.get_funcname(func) orig_funcname = modname.split('.')[-1] + '.' + funcname orig_docstr = '' if orig_docstr is None else orig_docstr import textwrap # Put wrapped function into a scope import inspect argspec = inspect.getargspec(func) (args, varargs, varkw, defaults) = argspec defsig = inspect.formatargspec(*argspec) callsig = inspect.formatargspec(*argspec[0:3]) src_fmtdict = dict(funcname=funcname, orig_funcname=orig_funcname, defsig=defsig, callsig=callsig, orig_docstr=orig_docstr) src = textwrap.dedent(src_fmt).format(**src_fmtdict) return src
def preserve_sig(wrapper, orig_func, force=False): """ Decorates a wrapper function. It seems impossible to presever signatures in python 2 without eval (Maybe another option is to write to a temporary module?) Args: wrapper: the function wrapping orig_func to change the signature of orig_func: the original function to take the signature from References: http://emptysqua.re/blog/copying-a-python-functions-signature/ https://code.google.com/p/micheles/source/browse/decorator/src/decorator.py TODO: checkout funcsigs https://funcsigs.readthedocs.org/en/latest/ CommandLine: python -m utool.util_decor --test-preserve_sig Example: >>> # ENABLE_DOCTEST >>> import utool as ut >>> #ut.rrrr(False) >>> def myfunction(self, listinput_, arg1, *args, **kwargs): >>> " just a test function " >>> return [x + 1 for x in listinput_] >>> #orig_func = ut.take >>> orig_func = myfunction >>> wrapper = ut.accepts_scalar_input2([0])(orig_func) >>> _wrp_preserve1 = ut.preserve_sig(wrapper, orig_func, True) >>> _wrp_preserve2 = ut.preserve_sig(wrapper, orig_func, False) >>> print('_wrp_preserve2 = %r' % (_wrp_preserve1,)) >>> print('_wrp_preserve2 = %r' % (_wrp_preserve2,)) >>> print('source _wrp_preserve1 = %s' % (ut.get_func_sourcecode(_wrp_preserve1),)) >>> print('source _wrp_preserve2 = %s' % (ut.get_func_sourcecode(_wrp_preserve2)),) >>> result = str(_wrp_preserve1) >>> print(result) """ #if True: # import functools # return functools.wraps(orig_func)(wrapper) from utool._internal import meta_util_six from utool import util_str from utool import util_inspect if wrapper is orig_func: # nothing to do return orig_func orig_docstr = meta_util_six.get_funcdoc(orig_func) orig_docstr = '' if orig_docstr is None else orig_docstr orig_argspec = util_inspect.get_func_argspec(orig_func) wrap_name = meta_util_six.get_funccode(wrapper).co_name orig_name = meta_util_six.get_funcname(orig_func) # At the very least preserve info in a dictionary _utinfo = {} _utinfo['orig_func'] = orig_func _utinfo['wrap_name'] = wrap_name _utinfo['orig_name'] = orig_name _utinfo['orig_argspec'] = orig_argspec if hasattr(wrapper, '_utinfo'): parent_wrapper_utinfo = wrapper._utinfo _utinfo['parent_wrapper_utinfo'] = parent_wrapper_utinfo if hasattr(orig_func, '_utinfo'): parent_orig_utinfo = orig_func._utinfo _utinfo['parent_orig_utinfo'] = parent_orig_utinfo # environment variable is set if you are building documentation # preserve sig if building docs building_docs = os.environ.get('UTOOL_AUTOGEN_SPHINX_RUNNING', 'OFF') == 'ON' if force or SIG_PRESERVE or building_docs: # PRESERVES ALL SIGNATURES WITH EXECS src_fmt = r''' def _wrp_preserve{defsig}: """ {orig_docstr} """ try: return wrapper{callsig} except Exception as ex: import utool as ut msg = ('Failure in signature preserving wrapper:\n') ut.printex(ex, msg) raise ''' # Put wrapped function into a scope globals_ = {'wrapper': wrapper} locals_ = {} # argspec is :ArgSpec(args=['bar', 'baz'], varargs=None, keywords=None, # defaults=(True,)) # get orig functions argspec # get functions signature # Get function call signature (no defaults) # Define an exec function argspec = inspect.getargspec(orig_func) (args, varargs, varkw, defaults) = argspec defsig = inspect.formatargspec(*argspec) callsig = inspect.formatargspec(*argspec[0:3]) # TODO: # ut.func_defsig # ut.func_callsig src_fmtdict = dict(defsig=defsig, callsig=callsig, orig_docstr=orig_docstr) src = textwrap.dedent(src_fmt).format(**src_fmtdict) # Define the new function on the fly # (I wish there was a non exec / eval way to do this) #print(src) code = compile(src, '<string>', 'exec') six.exec_(code, globals_, locals_) #six.exec_(src, globals_, locals_) # Use functools.update_wapper to complete preservation _wrp_preserve = functools.update_wrapper(locals_['_wrp_preserve'], orig_func) # Keep debug info _utinfo['src'] = src # Set an internal sig variable that we may use #_wrp_preserve.__sig__ = defsig else: # PRESERVES SOME SIGNATURES NO EXEC # signature preservation is turned off. just preserve the name. # Does not use any exec or eval statments. _wrp_preserve = functools.update_wrapper(wrapper, orig_func) # Just do something to preserve signature DEBUG_WRAPPED_DOCSTRING = False if DEBUG_WRAPPED_DOCSTRING: new_docstr_fmtstr = util_str.codeblock( ''' Wrapped function {wrap_name}({orig_name}) orig_argspec = {orig_argspec} orig_docstr = {orig_docstr} ''' ) else: new_docstr_fmtstr = util_str.codeblock( ''' {orig_docstr} ''' ) new_docstr = new_docstr_fmtstr.format( wrap_name=wrap_name, orig_name=orig_name, orig_docstr=orig_docstr, orig_argspec=orig_argspec) meta_util_six.set_funcdoc(_wrp_preserve, new_docstr) _wrp_preserve._utinfo = _utinfo return _wrp_preserve