osbot-utils 1.7.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- osbot_utils/__init__.py +1 -0
- osbot_utils/base_classes/Cache_Pickle.py +129 -0
- osbot_utils/base_classes/Kwargs_To_Disk.py +27 -0
- osbot_utils/base_classes/Kwargs_To_Self.py +308 -0
- osbot_utils/base_classes/Type_Safe__List.py +14 -0
- osbot_utils/base_classes/__init__.py +0 -0
- osbot_utils/context_managers/__init__.py +0 -0
- osbot_utils/context_managers/capture_duration.py +33 -0
- osbot_utils/decorators/__init__.py +0 -0
- osbot_utils/decorators/classes/__init__.py +0 -0
- osbot_utils/decorators/classes/singleton.py +9 -0
- osbot_utils/decorators/lists/__init__.py +0 -0
- osbot_utils/decorators/lists/filter_list.py +12 -0
- osbot_utils/decorators/lists/group_by.py +21 -0
- osbot_utils/decorators/lists/index_by.py +27 -0
- osbot_utils/decorators/methods/__init__.py +0 -0
- osbot_utils/decorators/methods/cache.py +19 -0
- osbot_utils/decorators/methods/cache_on_function.py +56 -0
- osbot_utils/decorators/methods/cache_on_self.py +78 -0
- osbot_utils/decorators/methods/cache_on_tmp.py +71 -0
- osbot_utils/decorators/methods/capture_exception.py +37 -0
- osbot_utils/decorators/methods/capture_status.py +20 -0
- osbot_utils/decorators/methods/catch.py +13 -0
- osbot_utils/decorators/methods/context.py +11 -0
- osbot_utils/decorators/methods/depreciated.py +79 -0
- osbot_utils/decorators/methods/function_type_check.py +62 -0
- osbot_utils/decorators/methods/obj_as_context.py +6 -0
- osbot_utils/decorators/methods/remove_return_value.py +22 -0
- osbot_utils/decorators/methods/required_fields.py +19 -0
- osbot_utils/fluent/Fluent_Dict.py +19 -0
- osbot_utils/fluent/Fluent_List.py +44 -0
- osbot_utils/fluent/__init__.py +1 -0
- osbot_utils/graphs/__init__.py +0 -0
- osbot_utils/graphs/mermaid/Mermaid.py +75 -0
- osbot_utils/graphs/mermaid/Mermaid__Edge.py +49 -0
- osbot_utils/graphs/mermaid/Mermaid__Graph.py +93 -0
- osbot_utils/graphs/mermaid/Mermaid__Node.py +69 -0
- osbot_utils/graphs/mermaid/Mermaid__Renderer.py +54 -0
- osbot_utils/graphs/mermaid/configs/Mermaid__Edge__Config.py +7 -0
- osbot_utils/graphs/mermaid/configs/Mermaid__Node__Config.py +9 -0
- osbot_utils/graphs/mermaid/configs/Mermaid__Render__Config.py +7 -0
- osbot_utils/graphs/mermaid/examples/Mermaid_Examples__FlowChart.py +98 -0
- osbot_utils/graphs/mermaid/models/Mermaid__Diagram_Direction.py +9 -0
- osbot_utils/graphs/mermaid/models/Mermaid__Diagram__Type.py +17 -0
- osbot_utils/graphs/mermaid/models/Mermaid__Node__Shape.py +30 -0
- osbot_utils/graphs/mgraph/MGraph.py +53 -0
- osbot_utils/graphs/mgraph/MGraph__Config.py +7 -0
- osbot_utils/graphs/mgraph/MGraph__Data.py +139 -0
- osbot_utils/graphs/mgraph/MGraph__Edge.py +27 -0
- osbot_utils/graphs/mgraph/MGraph__Node.py +33 -0
- osbot_utils/graphs/mgraph/MGraph__Random_Graphs.py +27 -0
- osbot_utils/graphs/mgraph/MGraph__Serializer.py +43 -0
- osbot_utils/graphs/mgraph/MGraphs.py +17 -0
- osbot_utils/graphs/mgraph/__init__.py +0 -0
- osbot_utils/helpers/CPrint.py +98 -0
- osbot_utils/helpers/Dict_To_Attr.py +7 -0
- osbot_utils/helpers/Local_Cache.py +111 -0
- osbot_utils/helpers/Local_Caches.py +54 -0
- osbot_utils/helpers/Print_Table.py +369 -0
- osbot_utils/helpers/Python_Audit.py +45 -0
- osbot_utils/helpers/Random_Seed.py +27 -0
- osbot_utils/helpers/SCP.py +58 -0
- osbot_utils/helpers/SSH.py +151 -0
- osbot_utils/helpers/Type_Registry.py +16 -0
- osbot_utils/helpers/__init__.py +0 -0
- osbot_utils/helpers/ast/Ast.py +35 -0
- osbot_utils/helpers/ast/Ast_Base.py +124 -0
- osbot_utils/helpers/ast/Ast_Data.py +28 -0
- osbot_utils/helpers/ast/Ast_Load.py +62 -0
- osbot_utils/helpers/ast/Ast_Merge.py +26 -0
- osbot_utils/helpers/ast/Ast_Node.py +117 -0
- osbot_utils/helpers/ast/Ast_Visit.py +85 -0
- osbot_utils/helpers/ast/Call_Tree.py +38 -0
- osbot_utils/helpers/ast/__init__.py +145 -0
- osbot_utils/helpers/ast/nodes/Ast_Add.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Alias.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_And.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Argument.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Arguments.py +10 -0
- osbot_utils/helpers/ast/nodes/Ast_Assert.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Assign.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Attribute.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Aug_Assign.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Bin_Op.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Bool_Op.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Break.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Call.py +17 -0
- osbot_utils/helpers/ast/nodes/Ast_Class_Def.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Compare.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Comprehension.py +10 -0
- osbot_utils/helpers/ast/nodes/Ast_Constant.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Continue.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Dict.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Eq.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Except_Handler.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Expr.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_For.py +10 -0
- osbot_utils/helpers/ast/nodes/Ast_Function_Def.py +17 -0
- osbot_utils/helpers/ast/nodes/Ast_Generator_Exp.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Gt.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_GtE.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_If.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_If_Exp.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Import.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Import_From.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_In.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Is.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Is_Not.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Keyword.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Lambda.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_List.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_List_Comp.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Load.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Lt.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_LtE.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Mod.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Module.py +20 -0
- osbot_utils/helpers/ast/nodes/Ast_Mult.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Name.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Not.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Not_Eq.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Not_In.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Or.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Pass.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Pow.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Raise.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Return.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Set.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Slice.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Starred.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Store.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Sub.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Subscript.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Try.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Tuple.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Unary_Op.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_While.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_With.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_With_Item.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Yield.py +7 -0
- osbot_utils/helpers/ast/nodes/__init__.py +0 -0
- osbot_utils/helpers/html/Dict_To_Css.py +20 -0
- osbot_utils/helpers/html/Dict_To_Html.py +59 -0
- osbot_utils/helpers/html/Dict_To_Tags.py +88 -0
- osbot_utils/helpers/html/Html_To_Dict.py +75 -0
- osbot_utils/helpers/html/Html_To_Tag.py +20 -0
- osbot_utils/helpers/html/Tag__Base.py +91 -0
- osbot_utils/helpers/html/Tag__Body.py +5 -0
- osbot_utils/helpers/html/Tag__Div.py +5 -0
- osbot_utils/helpers/html/Tag__H.py +9 -0
- osbot_utils/helpers/html/Tag__HR.py +5 -0
- osbot_utils/helpers/html/Tag__Head.py +32 -0
- osbot_utils/helpers/html/Tag__Html.py +42 -0
- osbot_utils/helpers/html/Tag__Link.py +17 -0
- osbot_utils/helpers/html/Tag__Style.py +25 -0
- osbot_utils/helpers/html/__init__.py +0 -0
- osbot_utils/helpers/pubsub/Event__Queue.py +95 -0
- osbot_utils/helpers/pubsub/PubSub__Client.py +53 -0
- osbot_utils/helpers/pubsub/PubSub__Room.py +13 -0
- osbot_utils/helpers/pubsub/PubSub__Server.py +94 -0
- osbot_utils/helpers/pubsub/PubSub__Sqlite.py +24 -0
- osbot_utils/helpers/pubsub/__init__.py +0 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event.py +15 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Connect.py +7 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Disconnect.py +7 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Join_Room.py +8 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Leave_Room.py +8 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Message.py +7 -0
- osbot_utils/helpers/pubsub/schemas/Schema__PubSub__Client.py +8 -0
- osbot_utils/helpers/pubsub/schemas/__init__.py +0 -0
- osbot_utils/helpers/sqlite/Capture_Sqlite_Error.py +51 -0
- osbot_utils/helpers/sqlite/Sqlite__Cursor.py +87 -0
- osbot_utils/helpers/sqlite/Sqlite__Database.py +137 -0
- osbot_utils/helpers/sqlite/Sqlite__Field.py +70 -0
- osbot_utils/helpers/sqlite/Sqlite__Globals.py +5 -0
- osbot_utils/helpers/sqlite/Sqlite__Table.py +293 -0
- osbot_utils/helpers/sqlite/Sqlite__Table__Create.py +96 -0
- osbot_utils/helpers/sqlite/Temp_Sqlite__Database__Disk.py +17 -0
- osbot_utils/helpers/sqlite/Temp_Sqlite__Table.py +23 -0
- osbot_utils/helpers/sqlite/__init__.py +0 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests.py +214 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests__Patch.py +63 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Files.py +23 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Graph.py +47 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Json.py +83 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Local.py +20 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Requests.py +39 -0
- osbot_utils/helpers/sqlite/domains/__init__.py +0 -0
- osbot_utils/helpers/sqlite/domains/schemas/Schema__Table__Requests.py +12 -0
- osbot_utils/helpers/sqlite/domains/schemas/__init__.py +0 -0
- osbot_utils/helpers/sqlite/models/Sqlite__Field__Type.py +37 -0
- osbot_utils/helpers/sqlite/models/__init__.py +0 -0
- osbot_utils/helpers/sqlite/sample_data/Sqlite__Sample_Data__Chinook.py +116 -0
- osbot_utils/helpers/sqlite/sample_data/__init__.py +0 -0
- osbot_utils/helpers/sqlite/sql_builder/SQL_Builder.py +159 -0
- osbot_utils/helpers/sqlite/sql_builder/SQL_Builder__Select.py +12 -0
- osbot_utils/helpers/sqlite/sql_builder/__init__.py +0 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Config.py +63 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Edges.py +46 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Files.py +45 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Nodes.py +52 -0
- osbot_utils/helpers/sqlite/tables/__init__.py +0 -0
- osbot_utils/helpers/trace/Trace_Call.py +120 -0
- osbot_utils/helpers/trace/Trace_Call__Config.py +94 -0
- osbot_utils/helpers/trace/Trace_Call__Graph.py +26 -0
- osbot_utils/helpers/trace/Trace_Call__Handler.py +215 -0
- osbot_utils/helpers/trace/Trace_Call__Print_Lines.py +85 -0
- osbot_utils/helpers/trace/Trace_Call__Print_Traces.py +170 -0
- osbot_utils/helpers/trace/Trace_Call__Stack.py +166 -0
- osbot_utils/helpers/trace/Trace_Call__Stack_Node.py +59 -0
- osbot_utils/helpers/trace/Trace_Call__Stats.py +71 -0
- osbot_utils/helpers/trace/Trace_Call__View_Model.py +75 -0
- osbot_utils/helpers/trace/Trace_Files.py +33 -0
- osbot_utils/helpers/trace/__init__.py +0 -0
- osbot_utils/testing/Catch.py +54 -0
- osbot_utils/testing/Duration.py +69 -0
- osbot_utils/testing/Hook_Method.py +118 -0
- osbot_utils/testing/Log_To_Queue.py +46 -0
- osbot_utils/testing/Log_To_String.py +37 -0
- osbot_utils/testing/Logging.py +81 -0
- osbot_utils/testing/Patch_Print.py +52 -0
- osbot_utils/testing/Profiler.py +89 -0
- osbot_utils/testing/Stderr.py +19 -0
- osbot_utils/testing/Stdout.py +19 -0
- osbot_utils/testing/Temp_File.py +46 -0
- osbot_utils/testing/Temp_Folder.py +114 -0
- osbot_utils/testing/Temp_Sys_Path.py +13 -0
- osbot_utils/testing/Temp_Web_Server.py +83 -0
- osbot_utils/testing/Temp_Zip.py +45 -0
- osbot_utils/testing/Temp_Zip_In_Memory.py +90 -0
- osbot_utils/testing/Unit_Test.py +34 -0
- osbot_utils/testing/Unzip_File.py +30 -0
- osbot_utils/testing/__init__.py +0 -0
- osbot_utils/utils/Assert.py +52 -0
- osbot_utils/utils/Call_Stack.py +187 -0
- osbot_utils/utils/Csv.py +32 -0
- osbot_utils/utils/Dev.py +47 -0
- osbot_utils/utils/Exceptions.py +7 -0
- osbot_utils/utils/Files.py +528 -0
- osbot_utils/utils/Functions.py +113 -0
- osbot_utils/utils/Http.py +136 -0
- osbot_utils/utils/Int.py +6 -0
- osbot_utils/utils/Json.py +171 -0
- osbot_utils/utils/Json_Cache.py +59 -0
- osbot_utils/utils/Lists.py +198 -0
- osbot_utils/utils/Misc.py +496 -0
- osbot_utils/utils/Objects.py +341 -0
- osbot_utils/utils/Png.py +29 -0
- osbot_utils/utils/Process.py +73 -0
- osbot_utils/utils/Python_Logger.py +301 -0
- osbot_utils/utils/Status.py +79 -0
- osbot_utils/utils/Str.py +63 -0
- osbot_utils/utils/Version.py +16 -0
- osbot_utils/utils/Zip.py +97 -0
- osbot_utils/utils/__init__.py +16 -0
- osbot_utils/version +1 -0
- osbot_utils-1.7.7.dist-info/LICENSE +201 -0
- osbot_utils-1.7.7.dist-info/METADATA +46 -0
- osbot_utils-1.7.7.dist-info/RECORD +260 -0
- osbot_utils-1.7.7.dist-info/WHEEL +4 -0
@@ -0,0 +1,85 @@
|
|
1
|
+
from osbot_utils.utils.Lists import list_sorted
|
2
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
3
|
+
from osbot_utils.utils.Misc import ansi_text_visible_length
|
4
|
+
from osbot_utils.helpers.trace.Trace_Call__Config import Trace_Call__Config
|
5
|
+
from osbot_utils.helpers.trace.Trace_Call__Print_Traces import text_grey, text_bold_green, text_olive, text_light_grey
|
6
|
+
|
7
|
+
|
8
|
+
class Trace_Call__Print_Lines(Kwargs_To_Self):
|
9
|
+
|
10
|
+
config : Trace_Call__Config
|
11
|
+
view_model : list
|
12
|
+
|
13
|
+
def lines(self):
|
14
|
+
lines = []
|
15
|
+
for trace in self.view_model:
|
16
|
+
items = trace.get('lines')
|
17
|
+
leading_spaces = 0
|
18
|
+
if items:
|
19
|
+
line = items[0].get('line')
|
20
|
+
leading_spaces = len(line) - len(line.lstrip())
|
21
|
+
|
22
|
+
for line_data in items:
|
23
|
+
depth_padding = ' ' + ' ' * (line_data.get('stack_size') - 2) * 6 # this helps to align the code with the current depth (i.e. column alignment of code)
|
24
|
+
line_data['line'] = depth_padding + line_data.get('line')[leading_spaces:]
|
25
|
+
lines.append(line_data)
|
26
|
+
return list_sorted(lines, 'index')
|
27
|
+
|
28
|
+
def max_fields_length(self, items, *fields):
|
29
|
+
max_length = 0
|
30
|
+
for item in items:
|
31
|
+
method_sig = '.'.join(str(item.get(field, '')) for field in fields)
|
32
|
+
if len(method_sig) > max_length:
|
33
|
+
max_length = len(method_sig)
|
34
|
+
return max_length
|
35
|
+
|
36
|
+
def max_fields_value(self, items, *fields):
|
37
|
+
max_value = 0
|
38
|
+
for item in items:
|
39
|
+
values = [int(item.get(field, 0)) for field in fields if field in item and isinstance(item.get(field), int)] # This will create a list of integers for the given fields in the line
|
40
|
+
line_max = max(values) if values else 0 # Now find the max value from these integers
|
41
|
+
if line_max > max_value:
|
42
|
+
max_value = line_max
|
43
|
+
return max_value
|
44
|
+
|
45
|
+
def print_lines(self, ):
|
46
|
+
lines = self.lines()
|
47
|
+
print("--------- CALL TRACER (Lines)----------")
|
48
|
+
print(f"Here are the {len(lines)} lines captured\n")
|
49
|
+
|
50
|
+
max_length__sig = self.max_fields_length(lines, 'module', 'func_name') + 2
|
51
|
+
max_length__line = self.max_fields_length(lines, 'line' ) + self.max_fields_value (lines, 'stack_size' ) + 5 # this + 5 helps with the alignment of the larger line (so that it doesn't overflow the table)
|
52
|
+
max_length__self = self.max_fields_length(lines, 'self_local' )
|
53
|
+
print( 'âââââââŦâââââââŦâ' + 'â' * max_length__line +'âââŦâ' + 'â' * max_length__sig + 'ââŦâ' + 'â' * max_length__self + 'ââŦââââââââ ')
|
54
|
+
print(f"â # â Line â {'Source code':<{max_length__line}} â {'Method Class and Name':<{max_length__sig}} â {'Self object':<{max_length__self}} â Depth â ")
|
55
|
+
print( 'âââââââŧâââââââŧâ' + 'â' * max_length__line +'âââŧâ' + 'â'* max_length__sig + 'ââŧâ' + 'â' * max_length__self + 'ââŧâââââââ⤠')
|
56
|
+
for line_data in lines:
|
57
|
+
index = line_data.get('index')
|
58
|
+
func_name = line_data.get('func_name')
|
59
|
+
line_number = line_data.get('line_number')
|
60
|
+
module = line_data.get('module')
|
61
|
+
event = line_data.get('event')
|
62
|
+
line = line_data.get('line')
|
63
|
+
self_local = line_data.get('self_local') or ''
|
64
|
+
method_sig = f"{module}.{func_name}"
|
65
|
+
stack_size = line_data.get('stack_size') -1
|
66
|
+
|
67
|
+
text_depth = f'{stack_size:5}'
|
68
|
+
text_depth_padding = ' ' * ((stack_size-1) * 2)
|
69
|
+
text_index = f'{text_grey(index):12}'
|
70
|
+
text_line_no = f'{line_number:4}'
|
71
|
+
text_method_sig = f'{method_sig:{max_length__sig}}'
|
72
|
+
|
73
|
+
if event == 'call':
|
74
|
+
text_line = f'{text_bold_green(line)}'
|
75
|
+
else:
|
76
|
+
text_line = f'{text_light_grey(line)}'
|
77
|
+
|
78
|
+
text_line_padding = ' ' * (max_length__line - ansi_text_visible_length(text_line) - len(text_depth_padding))
|
79
|
+
text_source_code = f'{text_depth_padding}{text_line} {text_line_padding}'
|
80
|
+
|
81
|
+
print(f"â {text_index} â {text_line_no} â {text_source_code} â {text_method_sig} â {self_local:<{max_length__self}} â {text_depth} â")
|
82
|
+
|
83
|
+
print('âââââââ´âââââââ´ââ' + 'â' * max_length__line + 'ââ´â' + 'â' * max_length__sig + 'ââ´ââ' + 'â' * max_length__self + 'â´ââââââââ')
|
84
|
+
|
85
|
+
|
@@ -0,0 +1,170 @@
|
|
1
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
2
|
+
from osbot_utils.utils.Dev import pformat
|
3
|
+
from osbot_utils.helpers.trace.Trace_Call__Config import Trace_Call__Config
|
4
|
+
|
5
|
+
# ANSI escape codes #todo: refactor this color support to separate colors class
|
6
|
+
dark_mode = False
|
7
|
+
|
8
|
+
if dark_mode:
|
9
|
+
BOLD = "\033[1m\033[48;2;30;31;34m\033[38;2;255;255;255m" # dark mode
|
10
|
+
BLUE = "\033[48;2;30;31;34m\033[94m"
|
11
|
+
GREEN = "\033[48;2;30;31;34m\033[92m"
|
12
|
+
LIGHT_GREY = "\033[48;2;30;31;34m\033[38;2;130;130;130m"
|
13
|
+
OLIVE = "\033[48;2;30;31;34m\033[38;2;118;138;118m"
|
14
|
+
GREY = "\033[48;2;30;31;34m\033[90m"
|
15
|
+
|
16
|
+
else:
|
17
|
+
BOLD = "\033[1m"
|
18
|
+
BLUE = "\033[94m"
|
19
|
+
GREEN = "\033[92m"
|
20
|
+
LIGHT_GREY = "\033[38;2;120;120;120m"
|
21
|
+
OLIVE = "\033[38;2;138;148;138m" #"\033[38;2;118;138;118m"
|
22
|
+
GREY = "\033[90m"
|
23
|
+
|
24
|
+
RED = "\033[91m"
|
25
|
+
WHITE = "\033[97m"
|
26
|
+
RESET = "\033[0m"
|
27
|
+
|
28
|
+
text_blue = lambda text: f"{BLUE}{text}{RESET}"
|
29
|
+
text_bold = lambda text: f"{BOLD}{text}{RESET}"
|
30
|
+
text_bold_red = lambda text: f"{BOLD}{RED}{text}{RESET}"
|
31
|
+
text_bold_green = lambda text: f"{BOLD}{GREEN}{text}{RESET}"
|
32
|
+
text_bold_blue = lambda text: f"{BOLD}{BLUE}{text}{RESET}"
|
33
|
+
text_green = lambda text: f"{GREEN}{text}{RESET}"
|
34
|
+
text_grey = lambda text: f"{GREY}{text}{RESET}"
|
35
|
+
text_light_grey = lambda text: f"{BOLD}{LIGHT_GREY}{text}{RESET}"
|
36
|
+
text_olive = lambda text: f"{OLIVE}{text}{RESET}"
|
37
|
+
text_red = lambda text: f"{RED}{text}{RESET}"
|
38
|
+
text_white = lambda text: f"{WHITE}{text}{RESET}"
|
39
|
+
text_none = lambda text: f"{text}"
|
40
|
+
text_color = lambda text, color: f"{color}{text}{RESET}"
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
class Trace_Call__Print_Traces(Kwargs_To_Self):
|
45
|
+
|
46
|
+
config: Trace_Call__Config
|
47
|
+
|
48
|
+
def __init__(self, **kwargs):
|
49
|
+
super().__init__(**kwargs)
|
50
|
+
|
51
|
+
def formatted_local_data(self, local_data, formatted_line, emoji = 'đ'):
|
52
|
+
if local_data:
|
53
|
+
formatted_data = {}
|
54
|
+
max_key_length = 0 # Variable to store the length of the longest key
|
55
|
+
|
56
|
+
# First pass to format data and find the length of the longest key
|
57
|
+
for key, value in local_data.items():
|
58
|
+
if key.startswith('_'): # don't show internal methods
|
59
|
+
continue
|
60
|
+
# Convert objects to their type name
|
61
|
+
if isinstance(value, dict):
|
62
|
+
value = pformat(value) # convert dicts to string (so that they are impacted by self.self.print_max_string_length)
|
63
|
+
if not isinstance(value, (int, float, bool, str, dict)):
|
64
|
+
formatted_data[key] = (type(value).__name__, BLUE)
|
65
|
+
elif isinstance(value, str) and len(value) > self.config.print_max_string_length:
|
66
|
+
formatted_data[key] = (value[:self.config.print_max_string_length] + "...", GREEN) # Trim large strings
|
67
|
+
else:
|
68
|
+
formatted_data[key] = (value, GREEN)
|
69
|
+
|
70
|
+
# Update the maximum key length
|
71
|
+
if len(key) > max_key_length:
|
72
|
+
max_key_length = len(key)
|
73
|
+
|
74
|
+
def format_multiline(value, left_padding):
|
75
|
+
lines = str(value).split('\n')
|
76
|
+
indented_lines = [lines[0]] + [" " * (left_padding +1) + line for line in lines[1:]]
|
77
|
+
return '\nâ'.join(indented_lines)
|
78
|
+
|
79
|
+
padding = " " * len(formatted_line)
|
80
|
+
for key, (value, color) in formatted_data.items():
|
81
|
+
# Calculate the number of spaces needed for alignment
|
82
|
+
spaces = " " * (max_key_length - len(key))
|
83
|
+
var_name = f"{padding} {emoji} {text_light_grey(key)}{spaces} = "
|
84
|
+
value = format_multiline(value, len(var_name)- len(text_light_grey(''))) # this logic makes sure that the local's values are column aligned
|
85
|
+
print(f'â{var_name}{color}{value}{RESET}')
|
86
|
+
|
87
|
+
def print_lines(self, lines, formatted_line):
|
88
|
+
if lines:
|
89
|
+
padding = " " * len(formatted_line)
|
90
|
+
for line in lines:
|
91
|
+
index = line.get('index')
|
92
|
+
#func_name = line.get('func_name')
|
93
|
+
#module = line.get('module')
|
94
|
+
event = line.get('event')
|
95
|
+
line = line.get('line')
|
96
|
+
if event == 'call':
|
97
|
+
print(f"{padding} {text_grey(index):12} {text_bold_green(line)}")
|
98
|
+
else:
|
99
|
+
print(f"{padding} {text_grey(index):12} {text_olive(line)}")
|
100
|
+
|
101
|
+
def print_traces(self, view_model):
|
102
|
+
print()
|
103
|
+
print("--------- CALL TRACER ----------")
|
104
|
+
print(f"Here are the {len(view_model)} traces captured\n")
|
105
|
+
for idx, item in enumerate(view_model):
|
106
|
+
emoji = item.get('emoji' , '' )
|
107
|
+
extra_data = item.get('extra_data' , {} )
|
108
|
+
locals = item.get('locals' , {} )
|
109
|
+
method_name = item.get('method_name' , '' )
|
110
|
+
method_parent = item.get('method_parent' , '' )
|
111
|
+
parent_info = item.get('parent_info' , '' )
|
112
|
+
prefix = item.get('prefix' , '' )
|
113
|
+
tree_branch = item.get('tree_branch' , '' )
|
114
|
+
source_code = item.get('source_code' , '' )
|
115
|
+
source_code_caller = item.get('source_code_caller', '' )
|
116
|
+
#source_code_location = item.get('source_code_location') or ''
|
117
|
+
|
118
|
+
if self.config.show_method_class:
|
119
|
+
if self.config.show_parent_info:
|
120
|
+
method_name = f'{text_olive(parent_info)}.{text_bold(method_name)}'
|
121
|
+
else:
|
122
|
+
method_name = f'{text_olive(method_parent)}.{text_bold(method_name)}'
|
123
|
+
|
124
|
+
|
125
|
+
node_text = source_code or method_name
|
126
|
+
formatted_line = f"{prefix}{tree_branch}{emoji} {node_text}"
|
127
|
+
if self.config.print_duration:
|
128
|
+
duration = item.get('duration',0) * 1000 # todo: see if this can be optimised with the similar call below
|
129
|
+
duration_rounded = round(duration, 3)
|
130
|
+
padding_duration = self.config.print_padding_duration - len(formatted_line)
|
131
|
+
duration_text = "{:>{},.3f}ms".format(duration_rounded, padding_duration)
|
132
|
+
formatted_line += f' {text_grey(duration_text)} '
|
133
|
+
|
134
|
+
if self.config.with_duration_bigger_than:
|
135
|
+
duration = item.get('duration', 0)
|
136
|
+
if duration < self.config.with_duration_bigger_than:
|
137
|
+
continue
|
138
|
+
|
139
|
+
if False and self.config.trace_capture_source_code: # todo: fix show caller funcionality
|
140
|
+
|
141
|
+
if self.config.show_caller:
|
142
|
+
print(f"{prefix}{tree_branch}đŧī¸{text_bold(source_code_caller)}")
|
143
|
+
print(f"{prefix}{tree_branch}âĄī¸{emoji} {text_grey(node_text)}")
|
144
|
+
else:
|
145
|
+
print(f"{prefix}{tree_branch}âĄī¸{emoji} {text_bold(node_text)}")
|
146
|
+
|
147
|
+
# if self.config.show_source_code_path:
|
148
|
+
#
|
149
|
+
# raise Exception("to implement path_source_code_root")
|
150
|
+
# path_source_code_root = ...
|
151
|
+
#
|
152
|
+
# print(f" " * len(prefix), end=" ")
|
153
|
+
# fixed_source_code_location = source_code_location.replace(path_source_code_root, '')
|
154
|
+
# print(fixed_source_code_location)
|
155
|
+
else:
|
156
|
+
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
|
157
|
+
print(f"{text_bold(formatted_line)}") # Don't add "|" to the first line
|
158
|
+
else:
|
159
|
+
padding = " " * (self.config.print_padding_parent_info - len(formatted_line))
|
160
|
+
|
161
|
+
print(f"{text_bold(formatted_line)} {padding} {parent_info}")
|
162
|
+
|
163
|
+
if self.config.trace_capture_lines:
|
164
|
+
self.print_lines(item.get('lines'), f'{prefix}{tree_branch}')
|
165
|
+
|
166
|
+
if self.config.print_locals:
|
167
|
+
self.formatted_local_data(locals, f'{prefix}{tree_branch}')
|
168
|
+
|
169
|
+
if self.config.capture_extra_data:
|
170
|
+
self.formatted_local_data(extra_data, f'{prefix}{tree_branch}', emoji='â¨')
|
@@ -0,0 +1,166 @@
|
|
1
|
+
import linecache
|
2
|
+
import time
|
3
|
+
from copy import deepcopy
|
4
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
5
|
+
from osbot_utils.helpers.trace.Trace_Call__Config import Trace_Call__Config
|
6
|
+
from osbot_utils.helpers.trace.Trace_Call__Stack_Node import Trace_Call__Stack_Node
|
7
|
+
|
8
|
+
|
9
|
+
class Trace_Call__Stack(Kwargs_To_Self):
|
10
|
+
call_index : int
|
11
|
+
stack_data : list
|
12
|
+
config : Trace_Call__Config
|
13
|
+
root_node : Trace_Call__Stack_Node
|
14
|
+
line_index : int
|
15
|
+
|
16
|
+
def __eq__(self, target):
|
17
|
+
if self is target:
|
18
|
+
return True
|
19
|
+
return self.stack_data == target
|
20
|
+
|
21
|
+
def __init__(self, **kwargs):
|
22
|
+
super().__init__(**kwargs)
|
23
|
+
|
24
|
+
def __iter__(self):
|
25
|
+
return iter(self.stack_data)
|
26
|
+
|
27
|
+
def __getitem__(self, index):
|
28
|
+
if -len(self.stack_data) <= index < len(self.stack_data):
|
29
|
+
return self.stack_data[index]
|
30
|
+
|
31
|
+
def __len__(self):
|
32
|
+
return self.size()
|
33
|
+
|
34
|
+
def add_frame(self, frame):
|
35
|
+
if frame and frame.__class__.__name__=='frame':
|
36
|
+
self.call_index += 1 # Increment the call index
|
37
|
+
code = frame.f_code # Get code object from frame
|
38
|
+
func_name = code.co_name # Get function name
|
39
|
+
module = frame.f_globals.get("__name__", "") # Get module name
|
40
|
+
|
41
|
+
source_code = self.map_source_code(frame)
|
42
|
+
full_name = self.map_full_name(frame, module, func_name)
|
43
|
+
new_node = self.create_stack_node(frame, full_name, source_code, self.call_index)
|
44
|
+
if self.add_stack_node(new_node, frame):
|
45
|
+
return new_node
|
46
|
+
|
47
|
+
|
48
|
+
def add_node(self, title: str):
|
49
|
+
new_node = self.new_stack_node(title)
|
50
|
+
if self.config.capture_duration:
|
51
|
+
new_node.call_start = time.perf_counter()
|
52
|
+
if self.add_stack_node(new_node):
|
53
|
+
return new_node
|
54
|
+
|
55
|
+
def add_stack_node(self, stack_node : Trace_Call__Stack_Node, frame=None):
|
56
|
+
if type(stack_node) is Trace_Call__Stack_Node:
|
57
|
+
if self.stack_data: # if there are items in the stack
|
58
|
+
self.top().children.append(stack_node) # add an xref to the new node to the children of the top node
|
59
|
+
else:
|
60
|
+
self.root_node = stack_node # if not this is the first node and capture it as a root node
|
61
|
+
self.stack_data.append(stack_node) # append the new node to the stack
|
62
|
+
return True
|
63
|
+
return False
|
64
|
+
|
65
|
+
def bottom(self):
|
66
|
+
if self.stack_data:
|
67
|
+
return self.stack_data[0]
|
68
|
+
|
69
|
+
def create_stack_node(self, frame, full_name, source_code, call_index):
|
70
|
+
new_node = Trace_Call__Stack_Node(call_index=call_index, name=full_name)
|
71
|
+
if frame:
|
72
|
+
code = frame.f_code
|
73
|
+
new_node.func_name = code.co_name # Get function name
|
74
|
+
new_node.module = frame.f_globals.get("__name__", "") # Get module name
|
75
|
+
if source_code:
|
76
|
+
new_node.source_code = source_code.get('source_code' )
|
77
|
+
new_node.source_code_caller = source_code.get('source_code_caller' )
|
78
|
+
new_node.source_code_location = source_code.get('source_code_location' )
|
79
|
+
|
80
|
+
if self.config.capture_frame:
|
81
|
+
new_node.frame = frame
|
82
|
+
if self.config.capture_locals:
|
83
|
+
if self.config.deep_copy_locals:
|
84
|
+
try:
|
85
|
+
new_node.locals = deepcopy(frame.f_locals)
|
86
|
+
except Exception as error:
|
87
|
+
new_node.locals = {'error': f'error in deepcopy: {error}'}
|
88
|
+
else:
|
89
|
+
new_node.locals = frame.f_locals
|
90
|
+
|
91
|
+
if self.config.capture_duration:
|
92
|
+
new_node.call_start = time.perf_counter()
|
93
|
+
return new_node
|
94
|
+
|
95
|
+
def empty_stack(self): # use to make sure the stack is empty (usally called at the end of a trace sessions)
|
96
|
+
for node in reversed(list(self.stack_data)):
|
97
|
+
self.pop(node)
|
98
|
+
return self
|
99
|
+
|
100
|
+
def map_source_code(self, frame):
|
101
|
+
if self.config.trace_capture_source_code:
|
102
|
+
filename = frame.f_code.co_filename
|
103
|
+
lineno = frame.f_lineno
|
104
|
+
source_code = linecache.getline(filename, lineno).strip()
|
105
|
+
|
106
|
+
caller_filename = frame.f_back.f_code.co_filename
|
107
|
+
caller_lineno = frame.f_back.f_lineno
|
108
|
+
source_code_caller = linecache.getline(caller_filename, caller_lineno).strip()
|
109
|
+
source_code_location = f'{filename}:{lineno}'
|
110
|
+
else:
|
111
|
+
source_code = ''
|
112
|
+
source_code_caller = ''
|
113
|
+
source_code_location = ''
|
114
|
+
|
115
|
+
return dict(source_code = source_code ,
|
116
|
+
source_code_caller = source_code_caller ,
|
117
|
+
source_code_location = source_code_location )
|
118
|
+
|
119
|
+
def map_full_name(self, frame, module, func_name):
|
120
|
+
if frame and module and func_name:
|
121
|
+
instance = frame.f_locals.get("self", None) # Get instance if available
|
122
|
+
try:
|
123
|
+
class_name = instance.__class__.__name__ if instance else ""
|
124
|
+
except Exception: # note: this will trigger this exception: ansi_text_visible_length("some text")
|
125
|
+
class_name = "<unavailable>"
|
126
|
+
if class_name:
|
127
|
+
full_name = f"{module}.{class_name}.{func_name}"
|
128
|
+
else:
|
129
|
+
full_name = f"{module}.{func_name}"
|
130
|
+
return full_name
|
131
|
+
|
132
|
+
def new_stack_node(self, name):
|
133
|
+
return Trace_Call__Stack_Node(call_index=self.call_index, name=name)
|
134
|
+
|
135
|
+
def nodes(self):
|
136
|
+
return self.stack_data
|
137
|
+
|
138
|
+
def remove_from_top(self, top_node, extra_data: dict):
|
139
|
+
if self.config.capture_duration:
|
140
|
+
top_node.call_end = time.perf_counter()
|
141
|
+
top_node.call_duration = top_node.call_end - top_node.call_start
|
142
|
+
if type(extra_data) is dict:
|
143
|
+
top_node.extra_data.update(extra_data)
|
144
|
+
self.stack_data.pop()
|
145
|
+
return True
|
146
|
+
|
147
|
+
def pop(self, target, extra_data: dict = None):
|
148
|
+
top_node = self.top()
|
149
|
+
if target and top_node :
|
150
|
+
if type(target) is Trace_Call__Stack_Node: # handle the case when target is Trace_Call__Stack_Node
|
151
|
+
if target == top_node: # if they match, pop the stack (since we are only capturing a subset of the stack)
|
152
|
+
return self.remove_from_top(top_node, extra_data)
|
153
|
+
elif target is top_node.frame: # if not assume target is a frame
|
154
|
+
return self.remove_from_top(top_node, extra_data) # if they match, pop the stack (since we are only capturing a subset of the stack)
|
155
|
+
return False
|
156
|
+
|
157
|
+
def push(self, frame):
|
158
|
+
return self.add_frame(frame)
|
159
|
+
|
160
|
+
|
161
|
+
def top(self):
|
162
|
+
if self.stack_data:
|
163
|
+
return self.stack_data[-1]
|
164
|
+
|
165
|
+
def size(self):
|
166
|
+
return self.stack_data.__len__()
|
@@ -0,0 +1,59 @@
|
|
1
|
+
from osbot_utils.utils.Misc import random_id
|
2
|
+
|
3
|
+
from osbot_utils.utils.Dev import pprint
|
4
|
+
|
5
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
6
|
+
|
7
|
+
EXTRA_DATA__RETURN_VALUE = '(return_value)'
|
8
|
+
|
9
|
+
class Trace_Call__Stack_Node(Kwargs_To_Self):
|
10
|
+
call_duration : float
|
11
|
+
call_end : float
|
12
|
+
call_index : int
|
13
|
+
call_start : float
|
14
|
+
children : list
|
15
|
+
extra_data : dict
|
16
|
+
locals : dict
|
17
|
+
frame : None
|
18
|
+
func_name : str
|
19
|
+
lines : list
|
20
|
+
key : str
|
21
|
+
module : str
|
22
|
+
name : str
|
23
|
+
source_code : str
|
24
|
+
source_code_caller : str
|
25
|
+
source_code_location: str
|
26
|
+
|
27
|
+
# def __init__(self, **kwargs):
|
28
|
+
# super().__init__(**kwargs)
|
29
|
+
# #self.key = random_id()
|
30
|
+
|
31
|
+
def __eq__(self, other):
|
32
|
+
if not isinstance(other, Trace_Call__Stack_Node):
|
33
|
+
return False
|
34
|
+
if self is other:
|
35
|
+
return True
|
36
|
+
return self.data() == other.data()
|
37
|
+
|
38
|
+
def __repr__(self):
|
39
|
+
return f'Trace_Call__Stack_Node (call_index={self.call_index})'
|
40
|
+
|
41
|
+
def all_children(self):
|
42
|
+
all_children = self.children.copy() # Initialize the list with the current node's children
|
43
|
+
for child in self.children: # Recursively add the children of each child node
|
44
|
+
all_children.extend(child.all_children())
|
45
|
+
return all_children
|
46
|
+
|
47
|
+
def info(self):
|
48
|
+
return f'Stack_Node: call_index:{self.call_index} | name: {self.name} | children: {len(self.children)} | source_code: {self.source_code is not None}'
|
49
|
+
|
50
|
+
def data(self):
|
51
|
+
return self.__locals__()
|
52
|
+
|
53
|
+
def print(self):
|
54
|
+
pprint(self.data())
|
55
|
+
|
56
|
+
def print_info(self):
|
57
|
+
pprint(self.info())
|
58
|
+
|
59
|
+
|
@@ -0,0 +1,71 @@
|
|
1
|
+
from collections import defaultdict, Counter
|
2
|
+
from copy import copy
|
3
|
+
|
4
|
+
from osbot_utils.utils.Dev import pprint
|
5
|
+
|
6
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
7
|
+
|
8
|
+
|
9
|
+
class Trace_Call__Stats(Kwargs_To_Self):
|
10
|
+
|
11
|
+
calls : int
|
12
|
+
calls_skipped : int
|
13
|
+
exceptions : int
|
14
|
+
lines : int
|
15
|
+
returns : int
|
16
|
+
unknowns : int # to use for extra events that are not being captured
|
17
|
+
raw_call_stats : list
|
18
|
+
|
19
|
+
def __repr__(self):
|
20
|
+
return str(self.stats())
|
21
|
+
|
22
|
+
def __eq__(self, target):
|
23
|
+
if self is target:
|
24
|
+
return True
|
25
|
+
return self.stats() == target
|
26
|
+
|
27
|
+
def log_frame(self, frame):
|
28
|
+
code = frame.f_code
|
29
|
+
func_name = code.co_name
|
30
|
+
module = frame.f_globals.get("__name__", "")
|
31
|
+
self.raw_call_stats.append((module, func_name))
|
32
|
+
return self
|
33
|
+
|
34
|
+
def frames_stats__build_tree(self, d, path, function_name):
|
35
|
+
parts = path.split('.')
|
36
|
+
current_level = d
|
37
|
+
for part in parts[:-1]: # Go up to the second-to-last element
|
38
|
+
if part not in current_level:
|
39
|
+
current_level[part] = {}
|
40
|
+
current_level = current_level[part]
|
41
|
+
if parts[-1] not in current_level:
|
42
|
+
current_level[parts[-1]] = Counter()
|
43
|
+
current_level[parts[-1]][function_name] += 1
|
44
|
+
|
45
|
+
def frames_stats(self):
|
46
|
+
processed_frame_stats = self.frames_stats__process_raw_data()
|
47
|
+
return self.to_standard_dict(processed_frame_stats)
|
48
|
+
|
49
|
+
def frames_stats__process_raw_data(self):
|
50
|
+
tree = defaultdict(dict)
|
51
|
+
for module, function_name in self.raw_call_stats:
|
52
|
+
self.frames_stats__build_tree(tree, module, function_name)
|
53
|
+
return tree
|
54
|
+
|
55
|
+
|
56
|
+
def to_standard_dict(self, d):
|
57
|
+
if isinstance(d, defaultdict):
|
58
|
+
d = {k: self.to_standard_dict(v) for k, v in d.items()}
|
59
|
+
if isinstance(d, Counter):
|
60
|
+
d = dict(d)
|
61
|
+
if isinstance(d, dict):
|
62
|
+
return {k: self.to_standard_dict(v) for k, v in d.items()}
|
63
|
+
return d
|
64
|
+
|
65
|
+
def stats(self):
|
66
|
+
stats = copy(self.__locals__())
|
67
|
+
del stats['raw_call_stats']
|
68
|
+
return stats
|
69
|
+
|
70
|
+
def print(self):
|
71
|
+
pprint(self.stats())
|
@@ -0,0 +1,75 @@
|
|
1
|
+
from osbot_utils.utils.Dev import pprint
|
2
|
+
|
3
|
+
|
4
|
+
class Trace_Call__View_Model:
|
5
|
+
|
6
|
+
def __init__(self):
|
7
|
+
self.view_model = []
|
8
|
+
|
9
|
+
def create(self, stack):
|
10
|
+
root_node = stack.root_node
|
11
|
+
if root_node:
|
12
|
+
target = [root_node]
|
13
|
+
self.view_model = self.create_view_model(target)
|
14
|
+
self.fix_view_mode() # Fix the view mode for the last node
|
15
|
+
return self.view_model
|
16
|
+
|
17
|
+
# todo: rename view_model so that it is not confused with self.view_model
|
18
|
+
def create_view_model(self, json_list, level=0, prefix="", view_model=None):
|
19
|
+
if view_model is None:
|
20
|
+
view_model = [] # Initialize view model if None
|
21
|
+
for idx, node in enumerate(json_list): # Iterate over each node in the JSON list to populate the view model
|
22
|
+
components = node.name.split('.')
|
23
|
+
duration = node.call_duration
|
24
|
+
extra_data = node.extra_data
|
25
|
+
frame_locals = node.locals
|
26
|
+
lines = node.lines
|
27
|
+
source_code = node.source_code
|
28
|
+
source_code_caller = node.source_code_caller
|
29
|
+
source_code_location = node.source_code_location
|
30
|
+
method_name = components[-1]
|
31
|
+
if len(components) > 1:
|
32
|
+
method_parent = f"{components[-2]}"
|
33
|
+
else:
|
34
|
+
method_parent = ""
|
35
|
+
if method_name == "__init__": # Adjust the method_name based on special method names like __init__ and __call__
|
36
|
+
method_name = f"{method_parent}.{method_name}"
|
37
|
+
elif method_name == "__call__":
|
38
|
+
method_name = f"{method_parent}.{method_name}"
|
39
|
+
elif method_name == "<module>":
|
40
|
+
method_name = f"{method_parent}.{method_name}"
|
41
|
+
|
42
|
+
pruned_parents = [comp for comp in components]
|
43
|
+
parent_info = '.'.join(pruned_parents[:-1])
|
44
|
+
|
45
|
+
if level == 0: # Handle tree representation at level 0
|
46
|
+
emoji = "đĻ "
|
47
|
+
tree_branch = ""
|
48
|
+
else:
|
49
|
+
is_last_sibling = (idx == len(json_list) - 1) # Check if the node is the last sibling
|
50
|
+
tree_branch = "âââ " if is_last_sibling else "âââ "
|
51
|
+
emoji = "đ§Šī¸" if not node.children else "đī¸"
|
52
|
+
|
53
|
+
view_model.append({ 'duration' : duration ,
|
54
|
+
'emoji' : emoji ,
|
55
|
+
'extra_data' : extra_data ,
|
56
|
+
'method_name' : method_name ,
|
57
|
+
'method_parent' : method_parent ,
|
58
|
+
'lines' : lines ,
|
59
|
+
'locals' : frame_locals , # todo finish refactoring use of locals to frame_locals
|
60
|
+
'parent_info' : parent_info ,
|
61
|
+
'prefix' : prefix ,
|
62
|
+
'source_code' : source_code ,
|
63
|
+
'source_code_caller' : source_code_caller ,
|
64
|
+
'source_code_location': source_code_location ,
|
65
|
+
'tree_branch' : tree_branch ,})
|
66
|
+
next_prefix = prefix + (" " if tree_branch == "âââ " else "â ") # Calculate the prefix for the next level
|
67
|
+
self.create_view_model(node.children, level + 1, prefix=next_prefix, view_model=view_model)
|
68
|
+
|
69
|
+
return view_model
|
70
|
+
|
71
|
+
def fix_view_mode(self):
|
72
|
+
if len(self.view_model) > 0: # these changes will provide a nice end of tree, for example replacing "â âââ" with "âââââââââââ "
|
73
|
+
last_node = self.view_model[-1] # Get the last node in the view model
|
74
|
+
last_node['prefix'] = last_node['prefix'].replace(' ', 'â').replace('â', 'â') # Update the prefix for the last node
|
75
|
+
last_node['tree_branch'] = 'âââ '
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from osbot_utils.helpers.trace.Trace_Call import Trace_Call
|
2
|
+
|
3
|
+
|
4
|
+
class Trace_Files(Trace_Call):
|
5
|
+
|
6
|
+
files: list
|
7
|
+
|
8
|
+
def __init__(self, **kwargs):
|
9
|
+
super().__init__(**kwargs)
|
10
|
+
|
11
|
+
# def trace_calls(self, frame, event, arg):
|
12
|
+
# if event == 'call':
|
13
|
+
# self.files.append(frame.f_code.co_filename)
|
14
|
+
#
|
15
|
+
# # if event != 'call':
|
16
|
+
# # return
|
17
|
+
# #
|
18
|
+
# #
|
19
|
+
# # # Get the function object being called
|
20
|
+
# # func = frame.f_globals.get(frame.f_code.co_name, None)
|
21
|
+
# #
|
22
|
+
# # # Retrieve the source code if the function object is available
|
23
|
+
# # if func:
|
24
|
+
# # try:
|
25
|
+
# # source_code = inspect.getsource(func)
|
26
|
+
# # print(f"Source code of {func.__name__}:\n{source_code}\n")
|
27
|
+
# # except TypeError:
|
28
|
+
# # pass # Handle cases where source code can't be retrieved
|
29
|
+
# #
|
30
|
+
#
|
31
|
+
# return super().trace_calls(frame, event, arg)
|
32
|
+
|
33
|
+
|
File without changes
|