# -*- coding: utf-8 -*-
from __future__ import absolute_import, division, print_function, unicode_literals
from six.moves import builtins
import inspect
import textwrap
import six
import sys
import functools
import os
from utool import util_print
from utool import util_time
from utool import util_iter
from utool import util_dbg
from utool import util_arg
from utool import util_type
from utool import util_inject
from utool._internal import meta_util_six
(print, rrr, profile) = util_inject.inject2(__name__, '[decor]')
if util_type.HAVE_NUMPY:
import numpy as np
# Commandline to toggle certain convinience decorators
SIG_PRESERVE = util_arg.get_argflag('--sigpreserve')
# SIG_PRESERVE = not util_arg.SAFE or util_arg.get_argflag('--sigpreserve')
ONEX_REPORT_INPUT = '--onex-report-input' in sys.argv
# IGNORE_TRACEBACK = '--smalltb' in sys.argv or '--ignoretb' in sys.argv
# FIXME: dupliated in _internal/py2_syntax_funcs
IGNORE_TRACEBACK = not ('--nosmalltb' in sys.argv or '--noignoretb' in sys.argv)
# if util_arg.STRICT:
# IGNORE_TRACEBACK = False
# do not ignore traceback when profiling
PROFILING = hasattr(builtins, 'profile')
UNIQUE_NUMPY = True
NOINDENT_DECOR = False
# os.environ.get('UTOOL_AUTOGEN_SPHINX_RUNNING', 'OFF')
# def composed(*decs):
# """ combines multiple decorators """
# def deco(f):
# for dec in reversed(decs):
# f = dec(f)
# return f
# return deco
[docs]def test_ignore_exec_traceback():
r"""
CommandLine:
python -m utool.util_decor --test-test_ignore_exec_traceback
Example:
>>> # ENABLE_DOCTEST
>>> from utool.util_decor import * # NOQA
>>> result = test_ignore_exec_traceback()
>>> print(result)
"""
import utool as ut
@ut.indent_func
def foobar():
print('foobar')
raise AssertionError('This error is exepcted')
try:
print('printing foobar')
foobar()
except AssertionError as ex:
# import sys
# exc_type, exc_value, exc_traceback = sys.exc_info()
# print(exc_traceback)
# TODO: ensure decorators are not printed in stack trace
ut.printex(ex, 'There is no error. This is a test', tb=True)
if six.PY2:
# Use version that has special python2 only syntax.
# can not include it here for that reason
from utool._internal import py2_syntax_funcs
ignores_exc_tb = py2_syntax_funcs.ignores_exc_tb
else:
[docs] def ignores_exc_tb(*args, **kwargs):
"""
PYTHON 3 VERSION
ignore_exc_tb decorates a function and remove both itself
and the function from any exception traceback that occurs.
This is useful to decorate other trivial decorators
which are polluting your stacktrace.
if IGNORE_TRACEBACK is False then this decorator does nothing
(and it should do nothing in production code!)
References:
https://github.com/jcrocholl/pep8/issues/34 # NOQA
http://legacy.python.org/dev/peps/pep-3109/
"""
outer_wrapper = kwargs.get('outer_wrapper', True)
def ignores_exc_tb_closure(func):
# HACK JUST TURN THIS OFF
return func
if not IGNORE_TRACEBACK:
# if the global enforces that we should not ignore anytracebacks
# then just return the original function without any modifcation
return func
# @wraps(func)
def wrp_noexectb(*args, **kwargs):
try:
# import utool
# if utool.DEBUG:
# print('[IN IGNORETB] args=%r' % (args,))
# print('[IN IGNORETB] kwargs=%r' % (kwargs,))
return func(*args, **kwargs)
except Exception:
# PYTHON 3.3 NEW METHODS
exc_type, exc_value, exc_traceback = sys.exc_info()
# Code to remove this decorator from traceback
# Remove two levels to remove this one as well
exc_type, exc_value, exc_traceback = sys.exc_info()
try:
exc_traceback = exc_traceback.tb_next
exc_traceback = exc_traceback.tb_next
except Exception:
pass
ex = exc_type(exc_value)
ex.__traceback__ = exc_traceback
raise ex
if outer_wrapper:
wrp_noexectb = preserve_sig(wrp_noexectb, func)
return wrp_noexectb
if len(args) == 1:
# called with one arg means its a function call
func = args[0]
return ignores_exc_tb_closure(func)
else:
# called with no args means kwargs as specified
return ignores_exc_tb_closure
# NEW PYTHON 2.7/3 VERSION
# def ignores_exc_tb(*args, **kwargs):
# """
# ignore_exc_tb decorates a function and remove both itself
# and the function from any exception traceback that occurs.
# This is useful to decorate other trivial decorators
# which are polluting your stacktrace.
# if IGNORE_TRACEBACK is False then this decorator does nothing
# (and it should do nothing in production code!)
# References:
# https://github.com/jcrocholl/pep8/issues/34 # NOQA
# http://legacy.python.org/dev/peps/pep-3109/
# """
# outer_wrapper = kwargs.get('outer_wrapper', True)
# def ignores_exc_tb_closure(func):
# if not IGNORE_TRACEBACK:
# # if the global enforces that we should not ignore anytracebacks
# # then just return the original function without any modifcation
# return func
# if six.PY2:
# #python2_func = """
# common_wrp_noexcept_tb = """
# def wrp_noexectb(*args, **kwargs):
# try:
# return func(*args, **kwargs)
# except Exception:
# exc_type, exc_value, exc_traceback = sys.exc_info()
# # Code to remove this decorator from traceback
# # Remove two levels to remove this one as well
# exc_type, exc_value, exc_traceback = sys.exc_info()
# try:
# exc_traceback = exc_traceback.tb_next
# exc_traceback = exc_traceback.tb_next
# except Exception:
# pass
# """
# if six.PY2:
# python2_reraise = """
# raise exc_type, exc_value, exc_traceback
# """
# six_reraise = python2_reraise
# elif six.PY3:
# python3_reraise = """
# ex = exc_type(exc_value)
# ex.__traceback__ = exc_traceback
# raise ex
# """
# six_reraise = python3_reraise
# wrp_noexcept_tb_codeblock = common_wrp_noexcept_tb + six_reraise
# globals_ = globals()
# locals_ = locals()
# six.exec_(wrp_noexcept_tb_codeblock, globals_, locals_)
# wrp_noexectb = locals_['wrp_noexectb']
# if outer_wrapper:
# wrp_noexectb = preserve_sig(wrp_noexectb, func)
# return wrp_noexectb
# if len(args) == 1:
# # called with one arg means its a function call
# func = args[0]
# return ignores_exc_tb_closure(func)
# else:
# # called with no args means kwargs as specified
# return ignores_exc_tb_closure
[docs]def debug_function_exceptions(func):
def _wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as ex:
import utool as ut
ut.printex(ex)
import inspect # NOQA
trace = inspect.trace()
locals_ = trace[-1][0].f_locals
print('-- <TRACE LOCALS> --')
for level, t in enumerate(trace[1:]):
frame = t[0]
locals_ = frame.f_locals
local_repr_dict = {
key: ut.trunc_repr(val) for key, val in locals_.items()
}
print('LOCALS LEVEL %d' % (level,))
print(ut.repr3(local_repr_dict, strvals=True, nl=1))
print('-- </TRACE LOCALS> --')
# import utool
# utool.embed()
raise
return _wrapper
# class DebugContext(object):
# def __enter__():
# pass
# def __exit__(self, exc_type, exc_value, exc_traceback):
# pass
def _indent_decor(lbl):
"""
does the actual work of indent_func
"""
def closure_indent(func):
if util_arg.TRACE:
@ignores_exc_tb(outer_wrapper=False)
# @wraps(func)
def wrp_indent(*args, **kwargs):
with util_print.Indenter(lbl):
print(' ...trace[in]')
ret = func(*args, **kwargs)
print(' ...trace[out]')
return ret
else:
@ignores_exc_tb(outer_wrapper=False)
# @wraps(func)
def wrp_indent(*args, **kwargs):
with util_print.Indenter(lbl):
ret = func(*args, **kwargs)
return ret
wrp_indent_ = ignores_exc_tb(wrp_indent)
wrp_indent_ = preserve_sig(wrp_indent, func)
return wrp_indent_
return closure_indent
[docs]def indent_func(input_):
"""
Takes either no arguments or an alias label
"""
if isinstance(input_, six.string_types):
# A label was specified
lbl = input_
return _indent_decor(lbl)
elif isinstance(input_, (bool, tuple)):
# Allow individually turning of of this decorator
func = input_
return func
else:
# Use the function name as the label
func = input_
lbl = '[' + meta_util_six.get_funcname(func) + ']'
return _indent_decor(lbl)(func)
[docs]def tracefunc_xml(func):
"""
Causes output of function to be printed in an XML style block
"""
funcname = meta_util_six.get_funcname(func)
def wrp_tracefunc2(*args, **kwargs):
verbose = kwargs.get('verbose', True)
if verbose:
print('<%s>' % (funcname,))
with util_print.Indenter(' '):
ret = func(*args, **kwargs)
if verbose:
print('</%s>' % (funcname,))
return ret
wrp_tracefunc2_ = ignores_exc_tb(wrp_tracefunc2)
wrp_tracefunc2_ = preserve_sig(wrp_tracefunc2_, func)
return wrp_tracefunc2_
# ----------
def __assert_param_consistency(args, argx_list_):
"""
debugging function for accepts_scalar_input2
checks to make sure all the iterable inputs are of the same length
"""
if util_arg.NO_ASSERTS:
return
if len(argx_list_) == 0:
return True
argx_flags = [util_iter.isiterable(args[argx]) for argx in argx_list_]
try:
assert all(
[argx_flags[0] == flag for flag in argx_flags]
), 'invalid mixing of iterable and scalar inputs'
except AssertionError as ex:
print('!!! ASSERTION ERROR IN UTIL_DECOR !!!')
for argx in argx_list_:
print('[util_decor] args[%d] = %r' % (argx, args[argx]))
raise ex
# TODO: Rename to listget_1to1 1toM etc...
getter_1to1 = accepts_scalar_input
getter_1toM = accepts_scalar_input_vector_output
# ----------
[docs]def accepts_numpy(func):
""" Allows the first input to be a numpy array and get result in numpy form """
# @ignores_exc_tb
# @wraps(func)
def wrp_accepts_numpy(self, input_, *args, **kwargs):
if not (util_type.HAVE_NUMPY and isinstance(input_, np.ndarray)):
# If the input is not numpy, just call the function
return func(self, input_, *args, **kwargs)
else:
# TODO: use a variant of util_list.unflat_unique_rowid_map
# If the input is a numpy array, and return the output with the same
# shape as the input
if UNIQUE_NUMPY:
# Remove redundant input (because we are passing it to SQL)
input_list, inverse_unique = np.unique(input_, return_inverse=True)
else:
input_list = input_.flatten()
# Call the function in list format
# TODO: is this necessary?
input_list = input_list.tolist()
output_list = func(self, input_list, *args, **kwargs)
# Put the output back into numpy
if UNIQUE_NUMPY:
# Reconstruct redundant queries
output_arr = np.array(output_list)[inverse_unique]
output_shape = tuple(list(input_.shape) + list(output_arr.shape[1:]))
return np.array(output_arr).reshape(output_shape)
else:
return np.array(output_list).reshape(input_.shape)
wrp_accepts_numpy = preserve_sig(wrp_accepts_numpy, func)
return wrp_accepts_numpy
[docs]def memoize_nonzero(func):
"""
Memoization decorator for functions taking a nonzero number of arguments.
References:
http://code.activestate.com/recipes/578231-fastest-memoization-decorator
"""
class _memorizer(dict):
def __init__(self, func):
self.func = func
def __call__(self, *args):
return self[args]
def __missing__(self, key):
ret = self[key] = self.func(*key)
return ret
return _memorizer(func)
[docs]def memoize_single(func):
""" Memoization decorator for a function taking a single argument
References:
http://code.activestate.com/recipes/578231-fastest-memoization-decorator
"""
class memodict_single(dict):
def __missing__(self, key):
ret = self[key] = func(key)
return ret
return memodict_single().__getitem__
[docs]def memoize_zero(func):
""" Memoization decorator for a function taking no arguments """
wrp_memoize_single = memoize_single(func)
def wrp_memoize_zero():
return wrp_memoize_single(None)
return wrp_memoize_zero
[docs]def memoize(func):
"""
simple memoization decorator
References:
https://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
Args:
func (function): live python function
Returns:
func:
CommandLine:
python -m utool.util_decor memoize
Example:
>>> # ENABLE_DOCTEST
>>> from utool.util_decor import * # NOQA
>>> import utool as ut
>>> closure = {'a': 'b', 'c': 'd'}
>>> incr = [0]
>>> def foo(key):
>>> value = closure[key]
>>> incr[0] += 1
>>> return value
>>> foo_memo = memoize(foo)
>>> assert foo('a') == 'b' and foo('c') == 'd'
>>> assert incr[0] == 2
>>> print('Call memoized version')
>>> assert foo_memo('a') == 'b' and foo_memo('c') == 'd'
>>> assert incr[0] == 4
>>> assert foo_memo('a') == 'b' and foo_memo('c') == 'd'
>>> print('Counter should no longer increase')
>>> assert incr[0] == 4
>>> print('Closure changes result without memoization')
>>> closure = {'a': 0, 'c': 1}
>>> assert foo('a') == 0 and foo('c') == 1
>>> assert incr[0] == 6
>>> assert foo_memo('a') == 'b' and foo_memo('c') == 'd'
"""
cache = func._util_decor_memoize_cache = {}
# @functools.wraps(func)
def memoizer(*args, **kwargs):
key = str(args) + str(kwargs)
if key not in cache:
cache[key] = func(*args, **kwargs)
return cache[key]
memoizer = preserve_sig(memoizer, func)
memoizer.cache = cache
return memoizer
[docs]def interested(func):
@indent_func
# @ignores_exc_tb
# @wraps(func)
def wrp_interested(*args, **kwargs):
sys.stdout.write('#\n')
sys.stdout.write('#\n')
sys.stdout.write('<!INTERESTED>: ' + meta_util_six.get_funcname(func) + '\n')
print('INTERESTING... ' + (' ' * 30) + ' <----')
return func(*args, **kwargs)
return wrp_interested
[docs]def tracefunc(func):
lbl = '[trace.' + meta_util_six.get_funcname(func) + ']'
def wrp_tracefunc(*args, **kwargs):
print(lbl + ' +--- ENTER ---')
with util_print.Indenter(lbl + ' |'):
ret = func(*args, **kwargs)
print(lbl + ' L___ EXIT ____')
return ret
return wrp_tracefunc
[docs]def show_return_value(func):
from utool.util_str import func_str
# @wraps(func)
def wrp_show_return_value(*args, **kwargs):
ret = func(*args, **kwargs)
# print('%s(*%r, **%r) returns %r' % (meta_util_six.get_funcname(func), args, kwargs, rv))
print(func_str(func, args, kwargs) + ' -> ret=%r' % (ret,))
return ret
return wrp_show_return_value
[docs]def time_func(func):
# @wraps(func)
def wrp_time(*args, **kwargs):
with util_time.Timer(meta_util_six.get_funcname(func)):
return func(*args, **kwargs)
wrp_time = preserve_sig(wrp_time, func)
return wrp_time
# def rename_func(newname):
# import utool as ut
# return ut.partial(ut.set_funcname, newname=newname)
# class copy_argspec(object):
# """
# copy_argspec is a signature modifying decorator.
# Specifically, it copies the signature from `source_func` to the wrapper, and
# the wrapper will call the original function (which should be using *args,
# **kwds). The argspec, docstring, and default values are copied from
# src_func, and __module__ and __dict__ from tgt_func.
# .. References
# http://stackoverflow.com/questions/18625510/how-can-i-programmatically-change-the-argspec-of-a-function-not-in-a-python-de
# """
# def __init__(self, src_func):
# self.argspec = inspect.getargspec(src_func)
# self.src_doc = src_func.__doc__
# self.src_defaults = src_func.func_defaults
# def __call__(self, tgt_func):
# try:
# tgt_argspec = inspect.getargspec(tgt_func)
# need_self = False
# if len(tgt_argspec) > 0 and len(tgt_argspec[0]) > 0 and tgt_argspec[0][0] == 'self':
# need_self = True
# name = tgt_func.__name__
# argspec = self.argspec
# if len(argspec) > 0 and len(argspec[0]) > 0 and argspec[0][0] == 'self':
# need_self = False
# if need_self:
# newargspec = (['self'] + argspec[0],) + argspec[1:]
# else:
# newargspec = argspec
# signature = inspect.formatargspec(formatvalue=lambda val: "",
# *newargspec)[1:-1]
# new_func = (
# 'def _wrapper_({signature}):\n'
# ' return {tgt_func}({signature})'
# ).format(signature=signature, tgt_func='tgt_func')
# evaldict = {'tgt_func' : tgt_func}
# exec new_func in evaldict
# wrapped = evaldict['_wrapper_']
# wrapped.__name__ = name
# wrapped.__doc__ = self.src_doc
# wrapped.func_defaults = self.src_defaults
# wrapped.__module__ = tgt_func.__module__
# wrapped.__dict__ = tgt_func.__dict__
# return wrapped
# except Exception as ex:
# util_dbg.printex(ex, 'error wrapping: %r' % (tgt_func,))
# raise
[docs]def lazyfunc(func):
"""
Returns a memcached version of a function
"""
closuremem_ = [{}]
def wrapper(*args, **kwargs):
mem = closuremem_[0]
key = (repr(args), repr(kwargs))
try:
return mem[key]
except KeyError:
mem[key] = func(*args, **kwargs)
return mem[key]
return wrapper
[docs]def apply_docstr(docstr_func):
"""
Changes docstr of one functio to that of another
"""
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
return docstr_applier
[docs]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
[docs]def dummy_args_decor(*args, **kwargs):
def dummy_args_closure(func):
return func
return dummy_args_closure
[docs]class classproperty(property):
"""
Decorates a method turning it into a classattribute
References:
https://stackoverflow.com/questions/1697501/python-staticmethod-with-property
"""
def __get__(self, cls, owner):
return classmethod(self.fget).__get__(None, owner)()
if __name__ == '__main__':
"""
CommandLine:
python -c "import utool, utool.util_decor; utool.doctest_funcs(utool.util_decor)"
python -m utool.util_decor
python -m utool.util_decor --allexamples
"""
import multiprocessing
multiprocessing.freeze_support() # for win32
import utool as ut # NOQA
ut.doctest_funcs()