from functools import partial as _partial, wraps as _wraps
from inspect import stack as _stack
from os.path import basename as _basename
from .string_handle import string_handle as _string_handle
class _Region:
def __init__(self, func=None, deferred_wrap_callback=None) -> None:
self.__function = func
self.__deferred_wrap_callback_function = deferred_wrap_callback
if self.__function is None:
self.__call_target = self.__wrap
elif callable(self.__function):
self.__call_target = self.__get_wrapper(self.__function)
_wraps(self.__function, updated=())(self)
else:
raise TypeError('func must be a callable object or None.')
def __get__(self, obj, objtype):
return _wraps(self)(self.__get_wrapper(self.__function, obj))
def __enter__(self) -> None:
self.begin()
def __exit__(self, *args) -> None:
self.end()
def __call__(self, *args, **kwargs):
return self.__call_target(*args, **kwargs)
def begin(self) -> None:
raise NotImplementedError()
def end(self) -> None:
raise NotImplementedError()
def __wrap(self, func):
if callable(func):
self.__function = func
else:
raise TypeError('Callable object is expected as a first argument.')
self.__call_wrap_callback()
return _wraps(self.__function)(self.__get_wrapper(self.__function))
def __call_wrap_callback(self):
if callable(self.__deferred_wrap_callback_function):
self.__deferred_wrap_callback_function(self.__function)
def __get_wrapper(self, func, obj=None):
if not callable(func):
raise TypeError('Callable object is expected to be passed.')
def _function_wrapper(*args, **kwargs):
self.begin()
try:
func_result = func(*args, **kwargs)
finally:
self.end()
return func_result
def _method_wrapper(*args, **kwargs):
self.begin()
try:
func_result = func(obj, *args, **kwargs)
finally:
self.end()
return func_result
return _function_wrapper if obj is None or isinstance(func, staticmethod) else _method_wrapper
class _CallSite:
CallerFrame = 1
def __init__(self, frame_number: int) -> None:
caller = _stack()[frame_number+1]
self._filename = _basename(caller.filename)
self._lineno = caller.lineno
def filename(self):
return self._filename
def lineno(self):
return self._lineno
class _NamedRegion(_Region):
def __init__(self, func=None, name_creation_callback=None) -> None:
super().__init__(self.__get_function(func), _partial(_NamedRegion.__deferred_wrap_callback, self))
self._name = self.__get_name(func)
self.__name_creation_callback = name_creation_callback
self.__is_final_name_determined = False
self.__is_custom_name_specified = isinstance(func, str)
final_name_can_be_determined_now = not (func is None or isinstance(func, _CallSite))
if final_name_can_be_determined_now:
self.__original_begin_func = None
self.__determine_final_name()
else:
self.__original_begin_func = self.begin
self.begin = self.__determine_final_name
def __str__(self) -> str:
return self._name
def __repr__(self) -> str:
return f"{self.__class__.__name__}('{self._name}')"
def begin(self) -> None:
raise NotImplementedError()
def end(self) -> None:
raise NotImplementedError()
def name(self):
return self._name
def __determine_final_name(self):
self.__is_final_name_determined = True
if callable(self.__name_creation_callback):
self.__name_creation_callback(self._name)
if self.__original_begin_func is not None:
self.begin = self.__original_begin_func
self.begin()
def __deferred_wrap_callback(self, func):
if not self.__is_final_name_determined:
self._name = self.__get_name(func)
self.__determine_final_name()
elif not self.__is_custom_name_specified:
raise RuntimeError('A custom name for a code region must be specified before'
' _NamedRegion.__call__() can be called more than once.')
@staticmethod
def __get_function(func):
return func if callable(func) else None
@staticmethod
def __get_name(func):
if func is None:
return None
if isinstance(func, str):
return _string_handle(func)
if isinstance(func, _CallSite):
return _string_handle(f'{func.filename()}:{func.lineno()}')
if hasattr(func, '__qualname__'):
return _string_handle(func.__qualname__)
if hasattr(func, '__name__'):
return _string_handle(func.__name__)
if hasattr(func, '__class__'):
return _string_handle(f'{func.__class__.__name__}.__call__')
raise ValueError('Cannot get the name for the code region.')