Module osbot_utils.helpers.trace.Trace_Call__Print_Traces

Expand source code
from osbot_utils.base_classes.Kwargs_To_Self        import Kwargs_To_Self
from osbot_utils.utils.Dev                          import pformat
from osbot_utils.helpers.trace.Trace_Call__Config   import Trace_Call__Config

# ANSI escape codes     #todo: refactor this color support to separate colors class
dark_mode = False

if dark_mode:
    BOLD       = "\033[1m\033[48;2;30;31;34m\033[38;2;255;255;255m"        # dark mode
    BLUE       = "\033[48;2;30;31;34m\033[94m"
    GREEN      = "\033[48;2;30;31;34m\033[92m"
    LIGHT_GREY = "\033[48;2;30;31;34m\033[38;2;130;130;130m"
    OLIVE      = "\033[48;2;30;31;34m\033[38;2;118;138;118m"
    GREY       = "\033[48;2;30;31;34m\033[90m"

else:
    BOLD  = "\033[1m"
    BLUE  = "\033[94m"
    GREEN = "\033[92m"
    LIGHT_GREY = "\033[38;2;120;120;120m"
    OLIVE = "\033[38;2;138;148;138m" #"\033[38;2;118;138;118m"
    GREY  = "\033[90m"

RED     = "\033[91m"
WHITE   = "\033[97m"
RESET   = "\033[0m"

text_blue       = lambda text: f"{BLUE}{text}{RESET}"
text_bold       = lambda text: f"{BOLD}{text}{RESET}"
text_bold_red   = lambda text: f"{BOLD}{RED}{text}{RESET}"
text_bold_green = lambda text: f"{BOLD}{GREEN}{text}{RESET}"
text_bold_blue  = lambda text: f"{BOLD}{BLUE}{text}{RESET}"
text_green      = lambda text: f"{GREEN}{text}{RESET}"
text_grey       = lambda text: f"{GREY}{text}{RESET}"
text_light_grey = lambda text: f"{BOLD}{LIGHT_GREY}{text}{RESET}"
text_olive      = lambda text: f"{OLIVE}{text}{RESET}"
text_red        = lambda text: f"{RED}{text}{RESET}"
text_white      = lambda text: f"{WHITE}{text}{RESET}"
text_none       = lambda text: f"{text}"
text_color      = lambda text, color: f"{color}{text}{RESET}"



class Trace_Call__Print_Traces(Kwargs_To_Self):

    config: Trace_Call__Config

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def formatted_local_data(self, local_data, formatted_line, emoji = 'šŸ”–'):
        if local_data:
            formatted_data = {}
            max_key_length = 0  # Variable to store the length of the longest key

            # First pass to format data and find the length of the longest key
            for key, value in local_data.items():
                if key.startswith('_'):                                                 # don't show internal methods
                    continue
                # Convert objects to their type name
                if isinstance(value, dict):
                    value = pformat(value)                                                  # convert dicts to string (so that they are impacted by self.self.print_max_string_length)
                if not isinstance(value, (int, float, bool, str, dict)):
                    formatted_data[key] = (type(value).__name__, BLUE)
                elif isinstance(value, str) and len(value) > self.config.print_max_string_length:
                    formatted_data[key] = (value[:self.config.print_max_string_length] + "...", GREEN)    # Trim large strings
                else:
                    formatted_data[key] = (value, GREEN)

                # Update the maximum key length
                if len(key) > max_key_length:
                    max_key_length = len(key)

            def format_multiline(value, left_padding):
                lines = str(value).split('\n')
                indented_lines = [lines[0]] + [" " * (left_padding +1) + line for line in lines[1:]]
                return '\nā”‚'.join(indented_lines)

            padding = " " * len(formatted_line)
            for key, (value, color) in formatted_data.items():
                # Calculate the number of spaces needed for alignment
                spaces = " " * (max_key_length - len(key))
                var_name = f"{padding}       {emoji} {text_light_grey(key)}{spaces} = "
                value = format_multiline(value, len(var_name)- len(text_light_grey('')))  # this logic makes sure that the local's values are column aligned
                print(f'ā”‚{var_name}{color}{value}{RESET}')

    def print_lines(self, lines, formatted_line):
        if lines:
            padding = " " * len(formatted_line)
            for line in lines:
                index       = line.get('index')
                #func_name   = line.get('func_name')
                #module      = line.get('module')
                event       = line.get('event')
                line        = line.get('line')
                if event == 'call':
                    print(f"{padding}       {text_grey(index):12} {text_bold_green(line)}")
                else:
                    print(f"{padding}       {text_grey(index):12} {text_olive(line)}")

    def print_traces(self, view_model):
        print()
        print("--------- CALL TRACER ----------")
        print(f"Here are the {len(view_model)} traces captured\n")
        for idx, item in enumerate(view_model):
            emoji                = item.get('emoji'             , '' )
            extra_data           = item.get('extra_data'        , {} )
            locals               = item.get('locals'            , {} )
            method_name          = item.get('method_name'       , '' )
            method_parent        = item.get('method_parent'     , '' )
            parent_info          = item.get('parent_info'       , '' )
            prefix               = item.get('prefix'            , '' )
            tree_branch          = item.get('tree_branch'       , '' )
            source_code          = item.get('source_code'       , '' )
            source_code_caller   = item.get('source_code_caller', '' )
            #source_code_location = item.get('source_code_location') or ''

            if self.config.show_method_class:
                if self.config.show_parent_info:
                    method_name = f'{text_olive(parent_info)}.{text_bold(method_name)}'
                else:
                    method_name = f'{text_olive(method_parent)}.{text_bold(method_name)}'


            node_text          = source_code or method_name
            formatted_line     = f"{prefix}{tree_branch}{emoji} {node_text}"
            if self.config.print_duration:
                duration         = item.get('duration',0) * 1000                    # todo: see if this can be optimised with the similar call below
                duration_rounded = round(duration, 3)
                padding_duration = self.config.print_padding_duration - len(formatted_line)
                duration_text    = "{:>{},.3f}ms".format(duration_rounded, padding_duration)
                formatted_line += f' {text_grey(duration_text)} '

            if self.config.with_duration_bigger_than:
                duration = item.get('duration', 0)
                if duration < self.config.with_duration_bigger_than:
                    continue

            if False and self.config.trace_capture_source_code:       # todo: fix show caller funcionality

                if self.config.show_caller:
                    print(f"{prefix}{tree_branch}šŸ”¼ļø{text_bold(source_code_caller)}")
                    print(f"{prefix}{tree_branch}āž”ļø{emoji} {text_grey(node_text)}")
                else:
                    print(f"{prefix}{tree_branch}āž”ļø{emoji} {text_bold(node_text)}")

                # if self.config.show_source_code_path:
                #
                #     raise Exception("to implement path_source_code_root")
                    # path_source_code_root = ...
                    #
                    # print(f" " * len(prefix), end="         ")
                    # fixed_source_code_location = source_code_location.replace(path_source_code_root, '')
                    # print(fixed_source_code_location)
            else:
                if idx == 0 or (self.config.show_parent_info is False or self.config.show_method_class is True):                            # Handle the first line and conditional parent info differently
                    print(f"{text_bold(formatted_line)}")                                                  # Don't add "|" to the first line
                else:
                    padding = " " * (self.config.print_padding_parent_info - len(formatted_line))

                    print(f"{text_bold(formatted_line)} {padding}         {parent_info}")

            if self.config.trace_capture_lines:
                self.print_lines(item.get('lines'), f'{prefix}{tree_branch}')

            if self.config.print_locals:
                self.formatted_local_data(locals, f'{prefix}{tree_branch}')

            if self.config.capture_extra_data:
                self.formatted_local_data(extra_data, f'{prefix}{tree_branch}', emoji='āœØ')

Functions

def text_blue(text)
Expand source code
text_blue       = lambda text: f"{BLUE}{text}{RESET}"
def text_bold(text)
Expand source code
text_bold       = lambda text: f"{BOLD}{text}{RESET}"
def text_bold_blue(text)
Expand source code
text_bold_blue  = lambda text: f"{BOLD}{BLUE}{text}{RESET}"
def text_bold_green(text)
Expand source code
text_bold_green = lambda text: f"{BOLD}{GREEN}{text}{RESET}"
def text_bold_red(text)
Expand source code
text_bold_red   = lambda text: f"{BOLD}{RED}{text}{RESET}"
def text_color(text, color)
Expand source code
text_color      = lambda text, color: f"{color}{text}{RESET}"
def text_green(text)
Expand source code
text_green      = lambda text: f"{GREEN}{text}{RESET}"
def text_grey(text)
Expand source code
text_grey       = lambda text: f"{GREY}{text}{RESET}"
def text_light_grey(text)
Expand source code
text_light_grey = lambda text: f"{BOLD}{LIGHT_GREY}{text}{RESET}"
def text_none(text)
Expand source code
text_none       = lambda text: f"{text}"
def text_olive(text)
Expand source code
text_olive      = lambda text: f"{OLIVE}{text}{RESET}"
def text_red(text)
Expand source code
text_red        = lambda text: f"{RED}{text}{RESET}"
def text_white(text)
Expand source code
text_white      = lambda text: f"{WHITE}{text}{RESET}"

Classes

class Trace_Call__Print_Traces (**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 Trace_Call__Print_Traces(Kwargs_To_Self):

    config: Trace_Call__Config

    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def formatted_local_data(self, local_data, formatted_line, emoji = 'šŸ”–'):
        if local_data:
            formatted_data = {}
            max_key_length = 0  # Variable to store the length of the longest key

            # First pass to format data and find the length of the longest key
            for key, value in local_data.items():
                if key.startswith('_'):                                                 # don't show internal methods
                    continue
                # Convert objects to their type name
                if isinstance(value, dict):
                    value = pformat(value)                                                  # convert dicts to string (so that they are impacted by self.self.print_max_string_length)
                if not isinstance(value, (int, float, bool, str, dict)):
                    formatted_data[key] = (type(value).__name__, BLUE)
                elif isinstance(value, str) and len(value) > self.config.print_max_string_length:
                    formatted_data[key] = (value[:self.config.print_max_string_length] + "...", GREEN)    # Trim large strings
                else:
                    formatted_data[key] = (value, GREEN)

                # Update the maximum key length
                if len(key) > max_key_length:
                    max_key_length = len(key)

            def format_multiline(value, left_padding):
                lines = str(value).split('\n')
                indented_lines = [lines[0]] + [" " * (left_padding +1) + line for line in lines[1:]]
                return '\nā”‚'.join(indented_lines)

            padding = " " * len(formatted_line)
            for key, (value, color) in formatted_data.items():
                # Calculate the number of spaces needed for alignment
                spaces = " " * (max_key_length - len(key))
                var_name = f"{padding}       {emoji} {text_light_grey(key)}{spaces} = "
                value = format_multiline(value, len(var_name)- len(text_light_grey('')))  # this logic makes sure that the local's values are column aligned
                print(f'ā”‚{var_name}{color}{value}{RESET}')

    def print_lines(self, lines, formatted_line):
        if lines:
            padding = " " * len(formatted_line)
            for line in lines:
                index       = line.get('index')
                #func_name   = line.get('func_name')
                #module      = line.get('module')
                event       = line.get('event')
                line        = line.get('line')
                if event == 'call':
                    print(f"{padding}       {text_grey(index):12} {text_bold_green(line)}")
                else:
                    print(f"{padding}       {text_grey(index):12} {text_olive(line)}")

    def print_traces(self, view_model):
        print()
        print("--------- CALL TRACER ----------")
        print(f"Here are the {len(view_model)} traces captured\n")
        for idx, item in enumerate(view_model):
            emoji                = item.get('emoji'             , '' )
            extra_data           = item.get('extra_data'        , {} )
            locals               = item.get('locals'            , {} )
            method_name          = item.get('method_name'       , '' )
            method_parent        = item.get('method_parent'     , '' )
            parent_info          = item.get('parent_info'       , '' )
            prefix               = item.get('prefix'            , '' )
            tree_branch          = item.get('tree_branch'       , '' )
            source_code          = item.get('source_code'       , '' )
            source_code_caller   = item.get('source_code_caller', '' )
            #source_code_location = item.get('source_code_location') or ''

            if self.config.show_method_class:
                if self.config.show_parent_info:
                    method_name = f'{text_olive(parent_info)}.{text_bold(method_name)}'
                else:
                    method_name = f'{text_olive(method_parent)}.{text_bold(method_name)}'


            node_text          = source_code or method_name
            formatted_line     = f"{prefix}{tree_branch}{emoji} {node_text}"
            if self.config.print_duration:
                duration         = item.get('duration',0) * 1000                    # todo: see if this can be optimised with the similar call below
                duration_rounded = round(duration, 3)
                padding_duration = self.config.print_padding_duration - len(formatted_line)
                duration_text    = "{:>{},.3f}ms".format(duration_rounded, padding_duration)
                formatted_line += f' {text_grey(duration_text)} '

            if self.config.with_duration_bigger_than:
                duration = item.get('duration', 0)
                if duration < self.config.with_duration_bigger_than:
                    continue

            if False and self.config.trace_capture_source_code:       # todo: fix show caller funcionality

                if self.config.show_caller:
                    print(f"{prefix}{tree_branch}šŸ”¼ļø{text_bold(source_code_caller)}")
                    print(f"{prefix}{tree_branch}āž”ļø{emoji} {text_grey(node_text)}")
                else:
                    print(f"{prefix}{tree_branch}āž”ļø{emoji} {text_bold(node_text)}")

                # if self.config.show_source_code_path:
                #
                #     raise Exception("to implement path_source_code_root")
                    # path_source_code_root = ...
                    #
                    # print(f" " * len(prefix), end="         ")
                    # fixed_source_code_location = source_code_location.replace(path_source_code_root, '')
                    # print(fixed_source_code_location)
            else:
                if idx == 0 or (self.config.show_parent_info is False or self.config.show_method_class is True):                            # Handle the first line and conditional parent info differently
                    print(f"{text_bold(formatted_line)}")                                                  # Don't add "|" to the first line
                else:
                    padding = " " * (self.config.print_padding_parent_info - len(formatted_line))

                    print(f"{text_bold(formatted_line)} {padding}         {parent_info}")

            if self.config.trace_capture_lines:
                self.print_lines(item.get('lines'), f'{prefix}{tree_branch}')

            if self.config.print_locals:
                self.formatted_local_data(locals, f'{prefix}{tree_branch}')

            if self.config.capture_extra_data:
                self.formatted_local_data(extra_data, f'{prefix}{tree_branch}', emoji='āœØ')

Ancestors

Class variables

var config :Ā Trace_Call__Config

Methods

def formatted_local_data(self, local_data, formatted_line, emoji='šŸ”–')
Expand source code
def formatted_local_data(self, local_data, formatted_line, emoji = 'šŸ”–'):
    if local_data:
        formatted_data = {}
        max_key_length = 0  # Variable to store the length of the longest key

        # First pass to format data and find the length of the longest key
        for key, value in local_data.items():
            if key.startswith('_'):                                                 # don't show internal methods
                continue
            # Convert objects to their type name
            if isinstance(value, dict):
                value = pformat(value)                                                  # convert dicts to string (so that they are impacted by self.self.print_max_string_length)
            if not isinstance(value, (int, float, bool, str, dict)):
                formatted_data[key] = (type(value).__name__, BLUE)
            elif isinstance(value, str) and len(value) > self.config.print_max_string_length:
                formatted_data[key] = (value[:self.config.print_max_string_length] + "...", GREEN)    # Trim large strings
            else:
                formatted_data[key] = (value, GREEN)

            # Update the maximum key length
            if len(key) > max_key_length:
                max_key_length = len(key)

        def format_multiline(value, left_padding):
            lines = str(value).split('\n')
            indented_lines = [lines[0]] + [" " * (left_padding +1) + line for line in lines[1:]]
            return '\nā”‚'.join(indented_lines)

        padding = " " * len(formatted_line)
        for key, (value, color) in formatted_data.items():
            # Calculate the number of spaces needed for alignment
            spaces = " " * (max_key_length - len(key))
            var_name = f"{padding}       {emoji} {text_light_grey(key)}{spaces} = "
            value = format_multiline(value, len(var_name)- len(text_light_grey('')))  # this logic makes sure that the local's values are column aligned
            print(f'ā”‚{var_name}{color}{value}{RESET}')
def print_lines(self, lines, formatted_line)
Expand source code
def print_lines(self, lines, formatted_line):
    if lines:
        padding = " " * len(formatted_line)
        for line in lines:
            index       = line.get('index')
            #func_name   = line.get('func_name')
            #module      = line.get('module')
            event       = line.get('event')
            line        = line.get('line')
            if event == 'call':
                print(f"{padding}       {text_grey(index):12} {text_bold_green(line)}")
            else:
                print(f"{padding}       {text_grey(index):12} {text_olive(line)}")
def print_traces(self, view_model)
Expand source code
def print_traces(self, view_model):
    print()
    print("--------- CALL TRACER ----------")
    print(f"Here are the {len(view_model)} traces captured\n")
    for idx, item in enumerate(view_model):
        emoji                = item.get('emoji'             , '' )
        extra_data           = item.get('extra_data'        , {} )
        locals               = item.get('locals'            , {} )
        method_name          = item.get('method_name'       , '' )
        method_parent        = item.get('method_parent'     , '' )
        parent_info          = item.get('parent_info'       , '' )
        prefix               = item.get('prefix'            , '' )
        tree_branch          = item.get('tree_branch'       , '' )
        source_code          = item.get('source_code'       , '' )
        source_code_caller   = item.get('source_code_caller', '' )
        #source_code_location = item.get('source_code_location') or ''

        if self.config.show_method_class:
            if self.config.show_parent_info:
                method_name = f'{text_olive(parent_info)}.{text_bold(method_name)}'
            else:
                method_name = f'{text_olive(method_parent)}.{text_bold(method_name)}'


        node_text          = source_code or method_name
        formatted_line     = f"{prefix}{tree_branch}{emoji} {node_text}"
        if self.config.print_duration:
            duration         = item.get('duration',0) * 1000                    # todo: see if this can be optimised with the similar call below
            duration_rounded = round(duration, 3)
            padding_duration = self.config.print_padding_duration - len(formatted_line)
            duration_text    = "{:>{},.3f}ms".format(duration_rounded, padding_duration)
            formatted_line += f' {text_grey(duration_text)} '

        if self.config.with_duration_bigger_than:
            duration = item.get('duration', 0)
            if duration < self.config.with_duration_bigger_than:
                continue

        if False and self.config.trace_capture_source_code:       # todo: fix show caller funcionality

            if self.config.show_caller:
                print(f"{prefix}{tree_branch}šŸ”¼ļø{text_bold(source_code_caller)}")
                print(f"{prefix}{tree_branch}āž”ļø{emoji} {text_grey(node_text)}")
            else:
                print(f"{prefix}{tree_branch}āž”ļø{emoji} {text_bold(node_text)}")

            # if self.config.show_source_code_path:
            #
            #     raise Exception("to implement path_source_code_root")
                # path_source_code_root = ...
                #
                # print(f" " * len(prefix), end="         ")
                # fixed_source_code_location = source_code_location.replace(path_source_code_root, '')
                # print(fixed_source_code_location)
        else:
            if idx == 0 or (self.config.show_parent_info is False or self.config.show_method_class is True):                            # Handle the first line and conditional parent info differently
                print(f"{text_bold(formatted_line)}")                                                  # Don't add "|" to the first line
            else:
                padding = " " * (self.config.print_padding_parent_info - len(formatted_line))

                print(f"{text_bold(formatted_line)} {padding}         {parent_info}")

        if self.config.trace_capture_lines:
            self.print_lines(item.get('lines'), f'{prefix}{tree_branch}')

        if self.config.print_locals:
            self.formatted_local_data(locals, f'{prefix}{tree_branch}')

        if self.config.capture_extra_data:
            self.formatted_local_data(extra_data, f'{prefix}{tree_branch}', emoji='āœØ')

Inherited members