# -*- coding: utf-8 -*-
"""
Injects code into live modules or into text source files.
Basic use case is to extend the print function into a logging function
"""
from __future__ import absolute_import, division, print_function, unicode_literals
from six.moves import builtins, range, zip, map # NOQA
import six # NOQA
import sys
import functools
from utool._internal import meta_util_six
from utool._internal import meta_util_arg
from utool import util_logging
import traceback
__AGGROFLUSH__ = '--aggroflush' in sys.argv
__LOGGING__ = '--logging' in sys.argv
__DEBUG_ALL__ = '--debug-all' in sys.argv
__DEBUG_PROF__ = '--debug-prof' in sys.argv or '--debug-profile' in sys.argv
DEBUG_PRINT = '--debug-print' in sys.argv
DEBUG_PRINT_N = meta_util_arg.get_argval('--debug-print-N', type_=str, default=None)
if DEBUG_PRINT_N is not None:
DEBUG_PRINT_N = list(map(int, DEBUG_PRINT_N.split(',')))
DEBUG_PRINT = True
elif DEBUG_PRINT:
DEBUG_PRINT_N = 0
QUIET = '--quiet' in sys.argv
SILENT = '--silent' in sys.argv
VERYVERBOSE = meta_util_arg.VERYVERBOSE
VERBOSE = meta_util_arg.VERBOSE
PRINT_INJECT_ORDER = meta_util_arg.PRINT_INJECT_ORDER # --verbinject
# only word
EXIT_ON_INJECT_MODNAME = meta_util_arg.get_argval(
'--exit-on-inject', type_=str, default=None
)
if __LOGGING__:
util_logging.start_logging()
# Read all flags with --debug in them
ARGV_DEBUG_FLAGS = []
for argv in sys.argv:
if argv.startswith('--debug-'):
ARGV_DEBUG_FLAGS.append(argv.replace('--debug-', '').replace('-', '_'))
# print('ARGV_DEBUG_FLAGS: %r' % (ARGV_DEBUG_FLAGS,))
# __STDOUT__ = sys.stdout
# __PRINT_FUNC__ = builtins.print
# __PRINT_DBG_FUNC__ = builtins.print
# __WRITE_FUNC__ = __STDOUT__.write
# __FLUSH_FUNC__ = __STDOUT__.flush
__RELOAD_OK__ = '--noreloadable' not in sys.argv
__INJECTED_MODULES__ = set([])
# Do not inject into these modules
__INJECT_BLACKLIST__ = frozenset(
['tri', 'gc', 'sys', 'string', 'types', '_dia', 'responce', 'six', __name__]
)
def _inject_funcs(module, *func_list):
for func in func_list:
if (
module is not None
and hasattr(module, '__name__')
and module.__name__ not in __INJECT_BLACKLIST__
and not module.__name__.startswith('six')
and not module.__name__.startswith('sys')
):
# print('setting: %s.%s = %r' % (module.__name__, meta_util_six.get_funcname(func), func))
setattr(module, meta_util_six.get_funcname(func), func)
def _add_injected_module(module):
global __INJECTED_MODULES__
__INJECTED_MODULES__.add(module)
[docs]def get_injected_modules():
return list(__INJECTED_MODULES__)
def _get_module(module_name=None, module=None, register=True):
"""finds module in sys.modules based on module name unless the module has
already been found and is passed in"""
if module is None and module_name is not None:
try:
module = sys.modules[module_name]
except KeyError as ex:
print(ex)
raise KeyError(
('module_name=%r must be loaded before ' + 'receiving injections')
% module_name
)
elif module is not None and module_name is None:
pass
else:
raise ValueError('module_name or module must be exclusively specified')
if register is True:
_add_injected_module(module)
return module
[docs]def colored_pygments_excepthook(type_, value, tb):
"""
References:
https://stackoverflow.com/questions/14775916/color-exceptions-python
CommandLine:
python -m utool.util_inject --test-colored_pygments_excepthook
"""
tbtext = ''.join(traceback.format_exception(type_, value, tb))
try:
from utool import util_str
formatted_text = util_str.highlight_text(tbtext, lexer_name='pytb', stripall=True)
except Exception:
# FIXME silent errro
formatted_text = tbtext
return sys.__excepthook__(type_, value, tb)
# import utool as ut
# if ut.SUPER_STRICT:
# raise
sys.stderr.write(formatted_text)
# EMBED_ON_ERROR = True
# Doesn't work
# if EMBED_ON_ERROR:
# import utool as ut
# ut.embed(N=1)
[docs]def inject_colored_exceptions():
"""
Causes exceptions to be colored if not already
Hooks into sys.excepthook
CommandLine:
python -m utool.util_inject --test-inject_colored_exceptions
Example:
>>> # DISABLE_DOCTEST
>>> from utool.util_inject import * # NOQA
>>> print('sys.excepthook = %r ' % (sys.excepthook,))
>>> #assert sys.excepthook is colored_pygments_excepthook, 'bad excepthook'
>>> raise Exception('should be in color')
"""
# COLORED_INJECTS = '--nocolorex' not in sys.argv
# COLORED_INJECTS = '--colorex' in sys.argv
# Ignore colored exceptions on win32
if VERBOSE:
print('[inject] injecting colored exceptions')
if not sys.platform.startswith('win32'):
if VERYVERBOSE:
print('[inject] injecting colored exceptions')
if '--noinject-color' in sys.argv:
print('Not injecting color')
else:
sys.excepthook = colored_pygments_excepthook
else:
if VERYVERBOSE:
print('[inject] cannot inject colored exceptions')
[docs]def make_module_print_func(module):
if SILENT:
def print(*args, **kwargs):
"""silent builtins.print"""
pass
else:
if DEBUG_PRINT:
# Turns on printing where a message came from
def print(*args, **kwargs):
"""debugging logging builtins.print"""
from utool._internal.meta_util_dbg import get_caller_name
calltag = ''.join(('[caller:', get_caller_name(N=DEBUG_PRINT_N), ']'))
util_logging._utool_print()(calltag, *args, **kwargs)
else:
def print(*args, **kwargs):
"""logging builtins.print"""
util_logging._utool_print()(*args, **kwargs)
return print
[docs]def make_module_write_func(module):
if SILENT:
def print_(*args, **kwargs):
"""silent stdout.write"""
pass
else:
if __AGGROFLUSH__:
def print_(*args, **kwargs):
"""aggressive logging stdout.write"""
util_logging._utool_write()(*args)
util_logging._utool_flush()()
else:
def print_(*args, **kwargs):
"""logging stdout.write"""
util_logging._utool_write()(*args)
return print_
[docs]def inject_print_functions(
module_name=None, module_prefix='[???]', DEBUG=False, module=None
):
"""
makes print functions to be injected into the module
"""
module = _get_module(module_name, module)
if SILENT:
def print(*args):
"""silent builtins.print"""
pass
def printDBG(*args):
"""silent debug print"""
pass
def print_(*args):
"""silent stdout.write"""
pass
else:
if DEBUG_PRINT:
# Turns on printing where a message came from
def print(*args):
"""debugging logging builtins.print"""
from utool._internal.meta_util_dbg import get_caller_name
calltag = ''.join(('[caller:', get_caller_name(N=DEBUG_PRINT_N), ']'))
util_logging._utool_print()(calltag, *args)
else:
def print(*args):
"""logging builtins.print"""
util_logging._utool_print()(*args)
if __AGGROFLUSH__:
def print_(*args):
"""aggressive logging stdout.write"""
util_logging._utool_write()(*args)
util_logging._utool_flush()()
else:
def print_(*args):
"""logging stdout.write"""
util_logging._utool_write()(*args)
# turn on module debugging with command line flags
dotpos = module.__name__.rfind('.')
if dotpos == -1:
module_name = module.__name__
else:
module_name = module.__name__[dotpos + 1 :]
def _replchars(str_):
return str_.replace('_', '-').replace(']', '').replace('[', '')
flag1 = '--debug-%s' % _replchars(module_name)
flag2 = '--debug-%s' % _replchars(module_prefix)
DEBUG_FLAG = any([flag in sys.argv for flag in [flag1, flag2]])
for curflag in ARGV_DEBUG_FLAGS:
if curflag in module_prefix:
DEBUG_FLAG = True
if __DEBUG_ALL__ or DEBUG or DEBUG_FLAG:
print('INJECT_PRINT: %r == %r' % (module_name, module_prefix))
def printDBG(*args):
"""debug logging print"""
msg = ', '.join(map(str, args))
util_logging.__UTOOL_PRINTDBG__(module_prefix + ' DEBUG ' + msg)
else:
def printDBG(*args):
"""silent debug logging print"""
pass
# _inject_funcs(module, print, print_, printDBG)
print_funcs = (print, print_, printDBG)
return print_funcs
[docs]def reload_module(module, verbose=None):
if not __RELOAD_OK__:
raise Exception('Reloading has been forced off')
if verbose is None:
verbose = 0 if QUIET else 1
try:
v = sys.version_info
if v.major >= 3 and v.minor >= 4:
import importlib
reload = importlib.reload
else:
import imp
reload = imp.reload
if verbose:
module_name = getattr(module, '__name__', '???')
builtins.print('RELOAD: module __name__=' + module_name)
reload(module)
except Exception as ex:
print(ex)
print('[util_inject] Failed to reload %r' % (module,))
raise
[docs]def make_module_reload_func(module_name=None, module_prefix='[???]', module=None):
"""Injects dynamic module reloading"""
module = _get_module(module_name, module, register=False)
if module_name is None:
module_name = str(module.__name__)
def rrr(verbose=True):
"""Dynamic module reloading"""
if not __RELOAD_OK__:
raise Exception('Reloading has been forced off')
try:
import imp
if verbose and not QUIET:
builtins.print(
'RELOAD: ' + str(module_prefix) + ' __name__=' + module_name
)
imp.reload(module)
except Exception as ex:
print(ex)
print('%s Failed to reload' % module_prefix)
raise
# this doesn't seem to set anything on import *
# _inject_funcs(module, rrr)
return rrr
[docs]def DUMMYPROF_FUNC(func):
"""dummy profiling func. does nothing"""
return func
[docs]def TIMERPROF_FUNC(func):
@functools.wraps(func)
def prof_wrapper(*args, **kwargs):
import utool as ut
with ut.Timer(meta_util_six.get_funcname(func)):
return func(*args, **kwargs)
# return ret
return prof_wrapper
if '--profile' in sys.argv:
# util_profile.make_profiler()
import line_profiler
PROFILE_FUNC = line_profiler.LineProfiler()
PROFILING = True
if __DEBUG_PROF__:
print('[util_inject] PROFILE ON')
else:
PROFILING = False
PROFILE_FUNC = DUMMYPROF_FUNC
# PROFILE_FUNC = TIMERPROF_FUNC
if __DEBUG_PROF__:
print('[util_inject] PROFILE OFF')
# Look in command line for functions to profile
PROF_FUNC_PAT_LIST = meta_util_arg.get_argval('--prof-func', type_=str, default=None)
if PROF_FUNC_PAT_LIST is not None:
PROF_FUNC_PAT_LIST = PROF_FUNC_PAT_LIST.split(',')
print('[util_inject] PROF_FUNC_PAT_LIST: %r' % (PROF_FUNC_PAT_LIST,))
# Look in command line for modules to profile
PROF_MOD_PAT_LIST = meta_util_arg.get_argval('--prof-mod', type_=str, default=None)
if PROF_MOD_PAT_LIST is not None:
PROF_MOD_PAT_LIST = PROF_MOD_PAT_LIST.split(',')
print('[util_inject] PROF_MOD_PAT_LIST: %r' % (PROF_MOD_PAT_LIST,))
[docs]def memprof(func):
"""requires memory_profiler
pip install memory_profiler
References:
https://pypi.python.org/pypi/memory_profiler
"""
import memory_profiler
return memory_profiler.profile(func)
def _matches_list(name, pat_list):
return any([name.find(pat) != -1 for pat in pat_list])
def _profile_func_flag(funcname):
"""checks if func has been requested to be profiled"""
if PROF_FUNC_PAT_LIST is None:
return True
return _matches_list(funcname, PROF_FUNC_PAT_LIST)
def _profile_module_flag(module_name):
"""checks if module has been requested to be profiled"""
if PROF_MOD_PAT_LIST is None:
return True
return _matches_list(module_name, PROF_MOD_PAT_LIST)
[docs]def make_module_profile_func(module_name=None, module_prefix='[???]', module=None):
# FIXME: not injecting right
module = _get_module(module_name, module)
if not _profile_module_flag(str(module)):
return DUMMYPROF_FUNC
# if module_name is None:
# return DUMMYPROF_FUNC
# profile_module_flag = PROF_MODULE_PAT is None or module_name.startswith(PROF_MODULE_PAT)
# if not profile_module_flag:
# return DUMMYPROF_FUNC
def profile_withfuncname_filter(func):
# Test to see if this function is specified
funcname = meta_util_six.get_funcname(func)
if _profile_func_flag(funcname):
if __DEBUG_PROF__:
print('profile func %r' % (func,))
# if isinstance(func, six.class_types):
# for k in func.__dict__.keys():
# if k.startswith('_'):
# continue
# v = getattr(func, k)
# if str(type(v)) == 'function':
# setattr(func, k, PROFILE_FUNC(v))
# else:
return PROFILE_FUNC(func)
return func
return profile_withfuncname_filter
DEBUG_SLOW_IMPORT = False
if DEBUG_SLOW_IMPORT:
# Find which modules take the longest to import
import ubelt as ub
tt = ub.Timer(verbose=False)
tt.tic()
import_times = {}
PREV_MODNAME = None
def check_debug_import_times():
import utool as ut
from utool import util_inject
print(
ut.align(
ut.repr4(ut.sort_dict(util_inject.import_times, 'vals'), precision=4), ':'
)
)
# ututil_inject.import_times
# pass
[docs]def noinject(
module_name=None, module_prefix='[???]', DEBUG=False, module=None, N=0, via=None
):
"""
Use in modules that do not have inject in them
Does not inject anything into the module. Just lets utool know that a module
is being imported so the import order can be debuged
"""
if PRINT_INJECT_ORDER:
from utool._internal import meta_util_dbg
callername = meta_util_dbg.get_caller_name(N=N + 1, strict=False)
lineno = meta_util_dbg.get_caller_lineno(N=N + 1, strict=False)
suff = ' via %s' % (via,) if via else ''
fmtdict = dict(
N=N, lineno=lineno, callername=callername, modname=module_name, suff=suff
)
msg = '[util_inject] N={N} {modname} is imported by {callername} at lineno={lineno}{suff}'.format(
**fmtdict
)
if DEBUG_SLOW_IMPORT:
global PREV_MODNAME
seconds = tt.toc()
import_times[(PREV_MODNAME, module_name)] = seconds
PREV_MODNAME = module_name
builtins.print(msg)
if DEBUG_SLOW_IMPORT:
tt.tic()
# builtins.print(elapsed)
if EXIT_ON_INJECT_MODNAME == module_name:
builtins.print('...exiting')
assert False, 'exit in inject requested'
[docs]def inject(module_name=None, module_prefix='[???]', DEBUG=False, module=None, N=1):
r"""
Injects your module with utool magic
Utool magic is not actually magic. It just turns your ``print`` statments into
logging statments, allows for your module to be used with the utool.Indent
context manager and the and utool.indent_func decorator. ``printDBG`` will soon
be deprecated as will ``print\_``. The function rrr is a developer convinience for
reloading the module dynamically durring runtime. The profile decorator is
a no-op if not using kernprof.py, otherwise it is kernprof.py's profile
decorator.
Args:
module_name (str): the __name__ varaible in your module
module_prefix (str): a user defined module prefix
DEBUG (bool):
module (None): the actual module (optional)
Returns:
tuple : (print, print\_, printDBG, rrr, profile\_)
Example:
>>> # DISABLE_DOCTEST
>>> from utool.util_inject import * # NOQA
>>> from __future__ import absolute_import, division, print_function, unicode_literals
>>> from util.util_inject import inject
>>> print, rrr, profile = inject2(__name__, '[mod]')
"""
# noinject(module_name, module_prefix, DEBUG, module, N=1)
noinject(module_name, module_prefix, DEBUG, module, N=N)
module = _get_module(module_name, module)
rrr = make_module_reload_func(None, module_prefix, module)
profile_ = make_module_profile_func(None, module_prefix, module)
print_funcs = inject_print_functions(None, module_prefix, DEBUG, module)
(print, print_, printDBG) = print_funcs
return (print, print_, printDBG, rrr, profile_)
[docs]def inject2(module_name=None, module_prefix=None, DEBUG=False, module=None, N=1):
r"""wrapper that depricates print\_ and printDBG"""
if module_prefix is None:
module_prefix = '[%s]' % (module_name,)
noinject(module_name, module_prefix, DEBUG, module, N=N)
module = _get_module(module_name, module)
rrr = make_module_reload_func(None, module_prefix, module)
profile_ = make_module_profile_func(None, module_prefix, module)
print = make_module_print_func(module)
return print, rrr, profile_
[docs]def split_python_text_into_lines(text):
"""
# TODO: make it so this function returns text so one statment is on one
# line that means no splitting up things like function definitions into
# multiple lines
"""
# import jedi
# script = jedi.Script(text, line=1, column=None, path='')
def parentesis_are_balanced(line):
"""
helper
References:
http://stackoverflow.com/questions/18007995/recursive-paren-balance
"""
def balanced(str_, i=0, cnt=0, left='(', right=')'):
if i == len(str_):
return cnt == 0
if cnt < 0:
return False
if str_[i] == left:
return balanced(str_, i + 1, cnt + 1)
elif str_[i] == right:
return balanced(str_, i + 1, cnt - 1)
return balanced(str_, i + 1, cnt)
return balanced(line)
lines = text.split('\n')
new_lines = []
current_line = ''
for line in lines:
current_line += line
if parentesis_are_balanced(current_line):
new_lines.append(current_line)
current_line = ''
return lines
[docs]def inject_python_code2(fpath, patch_code, tag):
"""Does autogeneration stuff"""
import utool as ut
text = ut.readfrom(fpath)
start_tag = '# <%s>' % tag
end_tag = '# </%s>' % tag
new_text = ut.replace_between_tags(text, patch_code, start_tag, end_tag)
ut.writeto(fpath, new_text)
[docs]def inject_python_code(fpath, patch_code, tag=None, inject_location='after_imports'):
"""
DEPRICATE
puts code into files on disk
"""
import utool as ut
assert tag is not None, 'TAG MUST BE SPECIFIED IN INJECTED CODETEXT'
text = ut.read_from(fpath)
comment_start_tag = '# <util_inject:%s>' % tag
comment_end_tag = '# </util_inject:%s>' % tag
tagstart_txtpos = text.find(comment_start_tag)
tagend_txtpos = text.find(comment_end_tag)
text_lines = ut.split_python_text_into_lines(text)
# split the file into two parts and inject code between them
if tagstart_txtpos != -1 or tagend_txtpos != -1:
assert tagstart_txtpos != -1, 'both tags must not be found'
assert tagend_txtpos != -1, 'both tags must not be found'
for pos, line in enumerate(text_lines):
if line.startswith(comment_start_tag):
tagstart_pos = pos
if line.startswith(comment_end_tag):
tagend_pos = pos
part1 = text_lines[0:tagstart_pos]
part2 = text_lines[tagend_pos + 1 :]
else:
if inject_location == 'after_imports':
first_nonimport_pos = 0
for line in text_lines:
list_ = ['import ', 'from ', '#', ' ']
isvalid = len(line) == 0 or any(line.startswith(str_) for str_ in list_)
if not isvalid:
break
first_nonimport_pos += 1
part1 = text_lines[0:first_nonimport_pos]
part2 = text_lines[first_nonimport_pos:]
else:
raise AssertionError('Unknown inject location')
newtext = (
'\n'.join(part1 + [comment_start_tag])
+ '\n'
+ patch_code
+ '\n'
+ '\n'.join([comment_end_tag] + part2)
)
text_backup_fname = fpath + '.' + ut.get_timestamp() + '.bak'
ut.write_to(text_backup_fname, text)
ut.write_to(fpath, newtext)
# print(newtext)
if '--inject-color' in sys.argv or '--cex' in sys.argv:
inject_colored_exceptions()
# Inject this module with itself!
print, rrr, profile = inject2(__name__, '[inject]')
if __name__ == '__main__':
"""
CommandLine:
python -m utool.util_inject
python -m utool.util_inject --allexamples
python -m utool.util_inject --allexamples --noface --nosrc
"""
import multiprocessing
multiprocessing.freeze_support() # for win32
import utool as ut # NOQA
ut.doctest_funcs()