Module osbot_utils.utils.Call_Stack
Expand source code
import linecache
import sys
import traceback
from osbot_utils.helpers.CPrint import CPrint
from osbot_utils.helpers.Print_Table import Print_Table, CHAR_TABLE_HORIZONTAL, CHAR_TABLE_TOP_LEFT, CHAR_TABLE_VERTICAL, CHAR_TABLE_BOTTOM_LEFT
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
from osbot_utils.utils.Objects import obj_data, class_full_name
MODULES_TO_STOP_CAPTURE = ['unittest.case']
PRINT_STACK_COLOR_THEMES = { 'default' : ('none' , 'none' , 'none' ),
'minty' : ('green' , 'grey' , 'cyan' ) ,
'meadow' : ('blue' , 'none' ,'green' ) ,
'aquamarine' : ('bright_cyan' , 'cyan' ,'blue' ),
'autumn' : ('bright_yellow' , 'yellow','red' )}
class Frame_Data(Kwargs_To_Self):
depth : int
caller_line : str
method_line : str
method_name : str
line_number : int
local_self : str
module : str
def __repr__(self):
return f"{self.data()})"
def data(self):
return self.__locals__()
class Call_Stack(Kwargs_To_Self):
capture_locals : bool = False
cprint : CPrint
cprint_theme : str = 'meadow'
frames : list
max_depth : int = 10
__print_order__: list = ['module', 'method_name', 'caller_line', 'method_line', 'local_self', 'line_number', 'depth']
def __repr__(self):
return "Call_Stack"
def calls(self):
calls = []
for frame in self.frames:
method_name = frame.method_name
module = frame.module
call = f"{module}.{method_name}"
calls.append(call)
return calls
def calls__source_code(self):
calls = []
for frame in self.frames:
calls.append(frame.caller_line)
return calls
def capture(self,skip_caller=True):
current_frame = sys._getframe().f_back
if skip_caller:
current_frame = current_frame.f_back
return self.capture_frame(current_frame)
def capture_frame(self, frame):
depth = 0
while frame:
if self.stop_capture(frame, depth):
break
new_frame = self.new_frame(frame,depth)
self.frames.append(new_frame)
depth += 1
frame = frame.f_back
return self
def stats(self):
return { 'depth' : len(self.frames) }
def stop_capture(self, frame, depth):
if frame is None:
return True
if depth > self.max_depth:
return True
module = frame.f_globals.get("__name__", "")
if module in MODULES_TO_STOP_CAPTURE:
return True
return False
def new_frame(self, frame, depth):
file_path = frame.f_code.co_filename
method_name = frame.f_code.co_name
caller_line_number = frame.f_lineno
method_line_number = frame.f_code.co_firstlineno
caller_line = linecache.getline(file_path, caller_line_number).strip()
method_line = linecache.getline(file_path, method_line_number).strip() # see if we need to add the code to resolve the function from the decorators
module = frame.f_globals.get("__name__", "")
local_self = class_full_name(frame.f_locals.get('self'))
frame_data = Frame_Data( depth = depth ,
caller_line = caller_line ,
method_line = method_line ,
method_name = method_name ,
module = module ,
local_self = local_self ,
line_number = caller_line_number )
if self.capture_locals:
frame_data.locals = frame.f_locals,
return frame_data
def print(self):
for line in self.stack_lines__calls():
print(line)
def print__source_code(self):
for line in self.stack_lines__source_code():
print(line)
def stack_lines(self, items):
self.cprint.lines = []
self.cprint.auto_print = False
if len(items) == 0:
return []
if len(items) == 1:
self.print_with_color('none', f"{CHAR_TABLE_HORIZONTAL} {items[0]}")
else:
color_top, color_middle, color_bottom = self.stack_colors()
self.print_with_color(color_top, text=f"{CHAR_TABLE_TOP_LEFT} {items[0]}")
for call in items[1:-1]:
self.print_with_color(color_middle, text=f"{CHAR_TABLE_VERTICAL} {call}")
self.print_with_color(color_bottom, text=f"{CHAR_TABLE_BOTTOM_LEFT} {items[-1]}")
return self.cprint.lines
def stack_lines__calls(self):
calls = self.calls()
return self.stack_lines(calls)
def stack_lines__source_code(self):
calls_source_code = self.calls__source_code()
return self.stack_lines(calls_source_code)
def stack_colors(self):
color_themes = PRINT_STACK_COLOR_THEMES
stack_colors = color_themes.get(self.cprint_theme)
if not stack_colors:
stack_colors = color_themes.get('default')
return stack_colors
def print_with_color(self, color_name, text):
self.cprint.__getattribute__(color_name)(text)
def print_table(self,headers_to_hide=None):
all_data = []
for frame in self.frames:
all_data.append(frame.data())
with Print_Table() as _:
_.add_data(all_data)
_.hide_headers(headers_to_hide)
_.print(order=self.__print_order__)
# static methods
# todo: refactor these static methods to be in a separate class and Call_Stack into the helpers folder
def call_stack_current_frame(return_caller=True):
if return_caller:
return sys._getframe().f_back
return sys._getframe()
def call_stack_format_stack(depth=None):
return traceback.format_stack(limit=depth)
def call_stack_frames(depth=None):
return traceback.extract_stack(limit=depth)
def call_stack_frames_data(depth=None):
frames_data = []
for frame in call_stack_frames(depth=depth):
frames_data.append(obj_data(frame))
return frames_data
def frames_in_threads():
return sys._current_frames()
Functions
def call_stack_current_frame(return_caller=True)
-
Expand source code
def call_stack_current_frame(return_caller=True): if return_caller: return sys._getframe().f_back return sys._getframe()
def call_stack_format_stack(depth=None)
-
Expand source code
def call_stack_format_stack(depth=None): return traceback.format_stack(limit=depth)
def call_stack_frames(depth=None)
-
Expand source code
def call_stack_frames(depth=None): return traceback.extract_stack(limit=depth)
def call_stack_frames_data(depth=None)
-
Expand source code
def call_stack_frames_data(depth=None): frames_data = [] for frame in call_stack_frames(depth=depth): frames_data.append(obj_data(frame)) return frames_data
def frames_in_threads()
-
Expand source code
def frames_in_threads(): return sys._current_frames()
Classes
class Call_Stack (**kwargs)
-
A mixin class to strictly assign keyword arguments to pre-defined instance attributes during initialization.
This base class provides an init method that assigns values from keyword arguments to instance attributes. If an attribute with the same name as a key from the kwargs is defined in the class, it will be set to the value from kwargs. If the key does not match any predefined attribute names, an exception is raised.
This behavior enforces strict control over the attributes of instances, ensuring that only predefined attributes can be set at the time of instantiation and avoids silent attribute creation which can lead to bugs in the code.
Usage
class MyConfigurableClass(Kwargs_To_Self): attribute1 = 'default_value' attribute2 = True attribute3 : str attribute4 : list attribute4 : int = 42
# Other methods can be added here
Correctly override default values by passing keyword arguments
instance = MyConfigurableClass(attribute1='new_value', attribute2=False)
This will raise an exception as 'attribute3' is not predefined
instance = MyConfigurableClass(attribute3='invalid_attribute')
this will also assign the default value to any variable that has a type defined. In the example above the default values (mapped by default__kwargs and locals) will be: attribute1 = 'default_value' attribute2 = True attribute3 = '' # default value of str attribute4 = [] # default value of list attribute4 = 42 # defined value in the class
Note
It is important that all attributes which may be set at instantiation are predefined in the class. Failure to do so will result in an exception being raised.
Methods
init(**kwargs): The initializer that handles the assignment of keyword arguments to instance attributes. It enforces strict attribute assignment rules, only allowing attributes that are already defined in the class to be set.
Initialize an instance of the derived class, strictly assigning provided keyword arguments to corresponding instance attributes.
Parameters
**kwargs: Variable length keyword arguments.
Raises
Exception
- If a key from kwargs does not correspond to any attribute pre-defined in the class, an exception is raised to prevent setting an undefined attribute.
Expand source code
class Call_Stack(Kwargs_To_Self): capture_locals : bool = False cprint : CPrint cprint_theme : str = 'meadow' frames : list max_depth : int = 10 __print_order__: list = ['module', 'method_name', 'caller_line', 'method_line', 'local_self', 'line_number', 'depth'] def __repr__(self): return "Call_Stack" def calls(self): calls = [] for frame in self.frames: method_name = frame.method_name module = frame.module call = f"{module}.{method_name}" calls.append(call) return calls def calls__source_code(self): calls = [] for frame in self.frames: calls.append(frame.caller_line) return calls def capture(self,skip_caller=True): current_frame = sys._getframe().f_back if skip_caller: current_frame = current_frame.f_back return self.capture_frame(current_frame) def capture_frame(self, frame): depth = 0 while frame: if self.stop_capture(frame, depth): break new_frame = self.new_frame(frame,depth) self.frames.append(new_frame) depth += 1 frame = frame.f_back return self def stats(self): return { 'depth' : len(self.frames) } def stop_capture(self, frame, depth): if frame is None: return True if depth > self.max_depth: return True module = frame.f_globals.get("__name__", "") if module in MODULES_TO_STOP_CAPTURE: return True return False def new_frame(self, frame, depth): file_path = frame.f_code.co_filename method_name = frame.f_code.co_name caller_line_number = frame.f_lineno method_line_number = frame.f_code.co_firstlineno caller_line = linecache.getline(file_path, caller_line_number).strip() method_line = linecache.getline(file_path, method_line_number).strip() # see if we need to add the code to resolve the function from the decorators module = frame.f_globals.get("__name__", "") local_self = class_full_name(frame.f_locals.get('self')) frame_data = Frame_Data( depth = depth , caller_line = caller_line , method_line = method_line , method_name = method_name , module = module , local_self = local_self , line_number = caller_line_number ) if self.capture_locals: frame_data.locals = frame.f_locals, return frame_data def print(self): for line in self.stack_lines__calls(): print(line) def print__source_code(self): for line in self.stack_lines__source_code(): print(line) def stack_lines(self, items): self.cprint.lines = [] self.cprint.auto_print = False if len(items) == 0: return [] if len(items) == 1: self.print_with_color('none', f"{CHAR_TABLE_HORIZONTAL} {items[0]}") else: color_top, color_middle, color_bottom = self.stack_colors() self.print_with_color(color_top, text=f"{CHAR_TABLE_TOP_LEFT} {items[0]}") for call in items[1:-1]: self.print_with_color(color_middle, text=f"{CHAR_TABLE_VERTICAL} {call}") self.print_with_color(color_bottom, text=f"{CHAR_TABLE_BOTTOM_LEFT} {items[-1]}") return self.cprint.lines def stack_lines__calls(self): calls = self.calls() return self.stack_lines(calls) def stack_lines__source_code(self): calls_source_code = self.calls__source_code() return self.stack_lines(calls_source_code) def stack_colors(self): color_themes = PRINT_STACK_COLOR_THEMES stack_colors = color_themes.get(self.cprint_theme) if not stack_colors: stack_colors = color_themes.get('default') return stack_colors def print_with_color(self, color_name, text): self.cprint.__getattribute__(color_name)(text) def print_table(self,headers_to_hide=None): all_data = [] for frame in self.frames: all_data.append(frame.data()) with Print_Table() as _: _.add_data(all_data) _.hide_headers(headers_to_hide) _.print(order=self.__print_order__)
Ancestors
Class variables
var capture_locals : bool
var cprint : CPrint
var cprint_theme : str
var frames : list
var max_depth : int
Methods
def calls(self)
-
Expand source code
def calls(self): calls = [] for frame in self.frames: method_name = frame.method_name module = frame.module call = f"{module}.{method_name}" calls.append(call) return calls
def calls__source_code(self)
-
Expand source code
def calls__source_code(self): calls = [] for frame in self.frames: calls.append(frame.caller_line) return calls
def capture(self, skip_caller=True)
-
Expand source code
def capture(self,skip_caller=True): current_frame = sys._getframe().f_back if skip_caller: current_frame = current_frame.f_back return self.capture_frame(current_frame)
def capture_frame(self, frame)
-
Expand source code
def capture_frame(self, frame): depth = 0 while frame: if self.stop_capture(frame, depth): break new_frame = self.new_frame(frame,depth) self.frames.append(new_frame) depth += 1 frame = frame.f_back return self
def new_frame(self, frame, depth)
-
Expand source code
def new_frame(self, frame, depth): file_path = frame.f_code.co_filename method_name = frame.f_code.co_name caller_line_number = frame.f_lineno method_line_number = frame.f_code.co_firstlineno caller_line = linecache.getline(file_path, caller_line_number).strip() method_line = linecache.getline(file_path, method_line_number).strip() # see if we need to add the code to resolve the function from the decorators module = frame.f_globals.get("__name__", "") local_self = class_full_name(frame.f_locals.get('self')) frame_data = Frame_Data( depth = depth , caller_line = caller_line , method_line = method_line , method_name = method_name , module = module , local_self = local_self , line_number = caller_line_number ) if self.capture_locals: frame_data.locals = frame.f_locals, return frame_data
def print(self)
-
Expand source code
def print(self): for line in self.stack_lines__calls(): print(line)
def print__source_code(self)
-
Expand source code
def print__source_code(self): for line in self.stack_lines__source_code(): print(line)
def print_table(self, headers_to_hide=None)
-
Expand source code
def print_table(self,headers_to_hide=None): all_data = [] for frame in self.frames: all_data.append(frame.data()) with Print_Table() as _: _.add_data(all_data) _.hide_headers(headers_to_hide) _.print(order=self.__print_order__)
def print_with_color(self, color_name, text)
-
Expand source code
def print_with_color(self, color_name, text): self.cprint.__getattribute__(color_name)(text)
def stack_colors(self)
-
Expand source code
def stack_colors(self): color_themes = PRINT_STACK_COLOR_THEMES stack_colors = color_themes.get(self.cprint_theme) if not stack_colors: stack_colors = color_themes.get('default') return stack_colors
def stack_lines(self, items)
-
Expand source code
def stack_lines(self, items): self.cprint.lines = [] self.cprint.auto_print = False if len(items) == 0: return [] if len(items) == 1: self.print_with_color('none', f"{CHAR_TABLE_HORIZONTAL} {items[0]}") else: color_top, color_middle, color_bottom = self.stack_colors() self.print_with_color(color_top, text=f"{CHAR_TABLE_TOP_LEFT} {items[0]}") for call in items[1:-1]: self.print_with_color(color_middle, text=f"{CHAR_TABLE_VERTICAL} {call}") self.print_with_color(color_bottom, text=f"{CHAR_TABLE_BOTTOM_LEFT} {items[-1]}") return self.cprint.lines
def stack_lines__calls(self)
-
Expand source code
def stack_lines__calls(self): calls = self.calls() return self.stack_lines(calls)
def stack_lines__source_code(self)
-
Expand source code
def stack_lines__source_code(self): calls_source_code = self.calls__source_code() return self.stack_lines(calls_source_code)
def stats(self)
-
Expand source code
def stats(self): return { 'depth' : len(self.frames) }
def stop_capture(self, frame, depth)
-
Expand source code
def stop_capture(self, frame, depth): if frame is None: return True if depth > self.max_depth: return True module = frame.f_globals.get("__name__", "") if module in MODULES_TO_STOP_CAPTURE: return True return False
Inherited members
class Frame_Data (**kwargs)
-
A mixin class to strictly assign keyword arguments to pre-defined instance attributes during initialization.
This base class provides an init method that assigns values from keyword arguments to instance attributes. If an attribute with the same name as a key from the kwargs is defined in the class, it will be set to the value from kwargs. If the key does not match any predefined attribute names, an exception is raised.
This behavior enforces strict control over the attributes of instances, ensuring that only predefined attributes can be set at the time of instantiation and avoids silent attribute creation which can lead to bugs in the code.
Usage
class MyConfigurableClass(Kwargs_To_Self): attribute1 = 'default_value' attribute2 = True attribute3 : str attribute4 : list attribute4 : int = 42
# Other methods can be added here
Correctly override default values by passing keyword arguments
instance = MyConfigurableClass(attribute1='new_value', attribute2=False)
This will raise an exception as 'attribute3' is not predefined
instance = MyConfigurableClass(attribute3='invalid_attribute')
this will also assign the default value to any variable that has a type defined. In the example above the default values (mapped by default__kwargs and locals) will be: attribute1 = 'default_value' attribute2 = True attribute3 = '' # default value of str attribute4 = [] # default value of list attribute4 = 42 # defined value in the class
Note
It is important that all attributes which may be set at instantiation are predefined in the class. Failure to do so will result in an exception being raised.
Methods
init(**kwargs): The initializer that handles the assignment of keyword arguments to instance attributes. It enforces strict attribute assignment rules, only allowing attributes that are already defined in the class to be set.
Initialize an instance of the derived class, strictly assigning provided keyword arguments to corresponding instance attributes.
Parameters
**kwargs: Variable length keyword arguments.
Raises
Exception
- If a key from kwargs does not correspond to any attribute pre-defined in the class, an exception is raised to prevent setting an undefined attribute.
Expand source code
class Frame_Data(Kwargs_To_Self): depth : int caller_line : str method_line : str method_name : str line_number : int local_self : str module : str def __repr__(self): return f"{self.data()})" def data(self): return self.__locals__()
Ancestors
Class variables
var caller_line : str
var depth : int
var line_number : int
var local_self : str
var method_line : str
var method_name : str
var module : str
Methods
def data(self)
-
Expand source code
def data(self): return self.__locals__()
Inherited members