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,369 @@
|
|
1
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
2
|
+
from osbot_utils.utils.Misc import ansi_text_visible_length
|
3
|
+
|
4
|
+
raw_data = """|-------------------------------------------------------------------------------------|
|
5
|
+
| BOTO3 REST calls (via BaseClient._make_api_call) |
|
6
|
+
|-------------------------------------------------------------------------------------|
|
7
|
+
| # | Method | Duration | Params | Return Value |
|
8
|
+
|-------------------------------------------------------------------------------------|
|
9
|
+
| 0 | GetCallerIdentity | 412 ms | ('GetCallerIdentity', {}) | {'UserId': 'AIDAW3B45JBMJ7OKHCQZL', 'Account': '470426667096', 'Arn': 'arn:aws:iam::470426667096:user/OSBot-AWS-Dev__Only-IAM'} |
|
10
|
+
| 1 | GetCallerIdentity | 97 ms | ('GetCallerIdentity', {}) | {'UserId': 'AIDAW3B45JBMJ7OKHCQZL', 'Account': '470426667096', 'Arn': 'arn:aws:iam::470426667096:user/OSBot-AWS-Dev__Only-IAM'} |
|
11
|
+
| 2 | GetCallerIdentity | 96 ms | ('GetCallerIdentity', {}) | {'UserId': 'AIDAW3B45JBMJ7OKHCQZL', 'Account': '470426667096', 'Arn': 'arn:aws:iam::470426667096:user/OSBot-AWS-Dev__Only-IAM'} |
|
12
|
+
|-------------------------------------------------------------------------------------|
|
13
|
+
| Total Duration: 0.73 secs | Total calls: 3 |
|
14
|
+
|-------------------------------------------------------------------------------------|
|
15
|
+
"""
|
16
|
+
|
17
|
+
CHAR_TABLE_HORIZONTAL = "─"
|
18
|
+
|
19
|
+
CHAR_TABLE_BOTTOM_LEFT = "└"
|
20
|
+
CHAR_TABLE_BOTTOM_RIGHT = "┘"
|
21
|
+
CHAR_TABLE_MIDDLE_LEFT = "├"
|
22
|
+
CHAR_TABLE_MIDDLE_RIGHT = "┤"
|
23
|
+
CHAR_TABLE_MIDDLE = "┼"
|
24
|
+
CHAR_TABLE_VERTICAL = "│"
|
25
|
+
CHAR_TABLE_TOP_LEFT = "┌"
|
26
|
+
CHAR_TABLE_TOP_RIGHT = "┐"
|
27
|
+
|
28
|
+
MAX_CELL_SIZE = 200
|
29
|
+
|
30
|
+
class Print_Table(Kwargs_To_Self):
|
31
|
+
title : str
|
32
|
+
headers : list
|
33
|
+
headers_by_index : dict
|
34
|
+
footer : str
|
35
|
+
headers_size : list
|
36
|
+
headers_to_hide : list
|
37
|
+
max_cell_size : int = MAX_CELL_SIZE
|
38
|
+
rows : list
|
39
|
+
rows_texts : list
|
40
|
+
table_width : int
|
41
|
+
text__all : list
|
42
|
+
text__footer : str
|
43
|
+
text__headers : str
|
44
|
+
text__table_bottom : str
|
45
|
+
text__table_middle : str
|
46
|
+
text__table_top : str
|
47
|
+
text__title : str
|
48
|
+
text__width : int
|
49
|
+
|
50
|
+
def __init__(self, **kwargs):
|
51
|
+
super().__init__(**kwargs)
|
52
|
+
|
53
|
+
def add_column(self, header, cells:list):
|
54
|
+
self.fix_table()
|
55
|
+
columns_count = len(self.headers)
|
56
|
+
self.add_header(header)
|
57
|
+
for index, cell in enumerate(cells):
|
58
|
+
if len(self.rows) <= index:
|
59
|
+
new_row = ['' for _ in range(columns_count)] + [cell]
|
60
|
+
self.rows.append(new_row)
|
61
|
+
else:
|
62
|
+
self.rows[index].append(cell)
|
63
|
+
return self
|
64
|
+
|
65
|
+
def add_data(self, data):
|
66
|
+
if type(data) is dict:
|
67
|
+
self.add_dict(data)
|
68
|
+
elif type(data) is list:
|
69
|
+
for item in data:
|
70
|
+
self.add_data(item)
|
71
|
+
else:
|
72
|
+
self.add_row(data)
|
73
|
+
return self
|
74
|
+
|
75
|
+
def add_dict(self, data:dict):
|
76
|
+
self.fix_table() # makes sure the number of headers and rows are the same
|
77
|
+
|
78
|
+
all_headers = set(self.headers) | set(data.keys()) # get all headers from the table and the data
|
79
|
+
for header in sorted(all_headers): # sorted to have consistent order of new headers (since without it the order is pseudo random)
|
80
|
+
if header not in self.headers: # to make sure the table headers and new data keys match
|
81
|
+
self.add_header(header) # add any new headers not already present
|
82
|
+
|
83
|
+
row_raw = {header: '' for header in all_headers} # Create a raw row with empty values for all headers
|
84
|
+
row_raw.update(data) # Update the raw row with values from data
|
85
|
+
row_by_header = [row_raw[header] for header in self.headers] # create a new row object, ensuring headers order
|
86
|
+
self.add_row(row_by_header) # add the new row to the table
|
87
|
+
return self
|
88
|
+
|
89
|
+
def add_header(self, header:str):
|
90
|
+
self.headers.append(header)
|
91
|
+
return self
|
92
|
+
|
93
|
+
def add_headers(self, *headers:list):
|
94
|
+
for header in headers:
|
95
|
+
self.add_header(header)
|
96
|
+
return self
|
97
|
+
|
98
|
+
def add_row(self, row:list):
|
99
|
+
if type(row) is not list:
|
100
|
+
self.rows.append([row])
|
101
|
+
else:
|
102
|
+
self.rows.append(row)
|
103
|
+
return self
|
104
|
+
|
105
|
+
def add_rows(self, rows:list):
|
106
|
+
for row in rows:
|
107
|
+
self.add_row(row)
|
108
|
+
return self
|
109
|
+
|
110
|
+
def calculate_max_cell_size(self, cell):
|
111
|
+
lines_len = []
|
112
|
+
for line in str(cell).split('\n'): # Split the cell into lines and find the maximum length of any line
|
113
|
+
line_len_ansi_visible = ansi_text_visible_length(line) # add support for the use of ansi chars (which impact the len calculations)
|
114
|
+
lines_len.append(line_len_ansi_visible)
|
115
|
+
max_cell_line_length = max(lines_len)
|
116
|
+
|
117
|
+
if max_cell_line_length > self.max_cell_size:
|
118
|
+
max_cell_line_length = self.max_cell_size
|
119
|
+
return max_cell_line_length
|
120
|
+
|
121
|
+
def fix_table(self):
|
122
|
+
if self.rows:
|
123
|
+
max_cells = max(len(row) for row in self.rows) # get max number of cells in any row
|
124
|
+
else:
|
125
|
+
max_cells = 0
|
126
|
+
|
127
|
+
extra_header_count = len(self.headers) + 1 # Start counting extra headers from the current number of headers
|
128
|
+
while len(self.headers) < max_cells: # Extend headers if necessary
|
129
|
+
self.headers.append(f"Header #{extra_header_count}") # headers cannot have empty values
|
130
|
+
extra_header_count += 1
|
131
|
+
for row in self.rows: # Ensure each row has the same number of cells as there are headers
|
132
|
+
while len(row) < len(self.headers):
|
133
|
+
row.append("")
|
134
|
+
for index, header in enumerate(self.headers): # capture the index of the headers
|
135
|
+
self.headers_by_index[index] = header
|
136
|
+
|
137
|
+
def hide_headers(self, headers):
|
138
|
+
self.headers_to_hide = headers
|
139
|
+
return self
|
140
|
+
|
141
|
+
def map_headers_size(self):
|
142
|
+
self.headers_size = [] # initialize the headers size with the size of each header
|
143
|
+
for header in self.headers:
|
144
|
+
header_len_ansi_visible = ansi_text_visible_length(header)
|
145
|
+
self.headers_size.append(header_len_ansi_visible)
|
146
|
+
|
147
|
+
for row in self.rows: # iterate over each row and update the headers size with the size of the largest cell
|
148
|
+
for index, cell in enumerate(row): # for each row
|
149
|
+
if cell: # Check if the cell is not empty or None
|
150
|
+
max_cell_line_length = self.calculate_max_cell_size(cell)
|
151
|
+
self.headers_size[index] = max(self.headers_size[index], max_cell_line_length) # Update the corresponding header size if this line is longer than the current max
|
152
|
+
|
153
|
+
# fix edge case that happens when the title or footer is longer than the table width
|
154
|
+
if len(self.headers_size):
|
155
|
+
last_header = len(self.headers_size) - 1 # get the index of the last header
|
156
|
+
last_header_size = self.headers_size[last_header] # get the size of the last header
|
157
|
+
all_headers_size = sum(self.headers_size) # get the size of all headers
|
158
|
+
all_headers_size_minus_last = all_headers_size - last_header_size # get the size of all headers minus the last header
|
159
|
+
|
160
|
+
if sum(self.headers_size) < len(self.title): # if the title is longer than the headers, update the last header size
|
161
|
+
title_size = len(self.title) # get the size of the title
|
162
|
+
new_last_header_size = title_size - all_headers_size_minus_last # calculate the new size of the last header
|
163
|
+
self.headers_size[last_header] = new_last_header_size # update the last header size
|
164
|
+
if sum(self.headers_size) < len(self.footer): # if the footer is longer than the headers, update the last header size
|
165
|
+
footer_size = len(self.footer) # get the size of the footer
|
166
|
+
new_last_header_size = footer_size - all_headers_size_minus_last # calculate the new size of the last header
|
167
|
+
self.headers_size[last_header] = new_last_header_size # update the last header size
|
168
|
+
return self
|
169
|
+
|
170
|
+
def map_table_width(self):
|
171
|
+
self.table_width = len(self.text__headers)
|
172
|
+
if len(self.footer) > self.table_width:
|
173
|
+
self.table_width = len(self.footer) + 4
|
174
|
+
if len(self.title) > self.table_width:
|
175
|
+
self.table_width = len(self.title) + 4
|
176
|
+
|
177
|
+
|
178
|
+
# def map_rows_texts(self):
|
179
|
+
# self.rows_texts = []
|
180
|
+
# if not self.rows:
|
181
|
+
# self.rows_texts = [f"{CHAR_TABLE_VERTICAL} {CHAR_TABLE_VERTICAL}"]
|
182
|
+
# else:
|
183
|
+
# for row in self.rows:
|
184
|
+
# row_text = CHAR_TABLE_VERTICAL
|
185
|
+
# for index, cell in enumerate(row):
|
186
|
+
# size = self.headers_size[index]
|
187
|
+
# row_text += f" {str(cell):{size}} {CHAR_TABLE_VERTICAL}"
|
188
|
+
# self.rows_texts.append(row_text)
|
189
|
+
# return self
|
190
|
+
|
191
|
+
def cell_value(self, cell_value):
|
192
|
+
cell_value = str(cell_value)
|
193
|
+
if len(cell_value) > self.max_cell_size:
|
194
|
+
return cell_value[:self.max_cell_size - 3] + '...'
|
195
|
+
return cell_value
|
196
|
+
|
197
|
+
def map_rows_texts(self):
|
198
|
+
self.rows_texts = []
|
199
|
+
#if not self.rows:
|
200
|
+
# self.rows_texts = [f"{CHAR_TABLE_VERTICAL}aaa{CHAR_TABLE_VERTICAL}"]
|
201
|
+
if self.rows:
|
202
|
+
for row in self.rows:
|
203
|
+
row_text = CHAR_TABLE_VERTICAL
|
204
|
+
additional_lines = [[] for _ in row] # Prepare to hold additional lines from multiline cells
|
205
|
+
for index, cell in enumerate(row):
|
206
|
+
if self.should_show_header(index):
|
207
|
+
size = self.headers_size[index]
|
208
|
+
cell_lines = str(cell).split('\n') # Split the cell text by newlines
|
209
|
+
cell_value = self.cell_value(cell_lines[0])
|
210
|
+
extra_padding = ' ' * (size - ansi_text_visible_length(cell_value))
|
211
|
+
row_text += f" {cell_value}{extra_padding} {CHAR_TABLE_VERTICAL}" # Add the first line of the cell
|
212
|
+
for i, line in enumerate(cell_lines[1:], start=1):
|
213
|
+
additional_lines[index].append(line) # Store additional lines
|
214
|
+
|
215
|
+
self.rows_texts.append(row_text)
|
216
|
+
|
217
|
+
# Handle additional lines by creating new row_texts for them
|
218
|
+
max_additional_lines = max(len(lines) for lines in additional_lines)
|
219
|
+
|
220
|
+
for depth in range(max_additional_lines):
|
221
|
+
extra_row_text = CHAR_TABLE_VERTICAL
|
222
|
+
for index, column in enumerate(additional_lines):
|
223
|
+
cell_data = column[depth] if len(column) > depth else ''
|
224
|
+
size = self.headers_size[index]
|
225
|
+
cell_value = self.cell_value(cell_data)
|
226
|
+
extra_padding = ' ' * (size - ansi_text_visible_length(cell_value))
|
227
|
+
extra_row_text += f" {cell_value}{extra_padding} {CHAR_TABLE_VERTICAL}"
|
228
|
+
self.rows_texts.append(extra_row_text)
|
229
|
+
|
230
|
+
return self
|
231
|
+
|
232
|
+
def map_text__all(self):
|
233
|
+
self.text__all = [ self.text__table_top ]
|
234
|
+
if self.title : self.text__all += [ self.text__title , self.text__table_middle ]
|
235
|
+
if self.headers : self.text__all += [ self.text__headers , self.text__table_middle ]
|
236
|
+
if self.rows : self.text__all += [ *self.rows_texts ]
|
237
|
+
if self.footer : self.text__all += [ self.text__table_middle , self.text__footer ]
|
238
|
+
self.text__all += [ self.text__table_bottom ]
|
239
|
+
|
240
|
+
def map_text__footer(self):
|
241
|
+
self.text__footer = f"{CHAR_TABLE_VERTICAL} {self.footer:{self.text__width}} {CHAR_TABLE_VERTICAL}"
|
242
|
+
|
243
|
+
def map_text__headers(self):
|
244
|
+
self.text__headers = CHAR_TABLE_VERTICAL
|
245
|
+
if not self.headers:
|
246
|
+
self.text__headers += f" {CHAR_TABLE_VERTICAL}"
|
247
|
+
else:
|
248
|
+
for header, size in zip(self.headers, self.headers_size):
|
249
|
+
if self.should_show_header(header):
|
250
|
+
self.text__headers += f" {header:{size}} {CHAR_TABLE_VERTICAL}"
|
251
|
+
return self
|
252
|
+
|
253
|
+
def map_text__table_bottom(self): self.text__table_bottom = f"{CHAR_TABLE_BOTTOM_LEFT}" + CHAR_TABLE_HORIZONTAL * (self.text__width + 2) + f"{CHAR_TABLE_BOTTOM_RIGHT }"
|
254
|
+
def map_text__table_middle(self): self.text__table_middle = f"{CHAR_TABLE_MIDDLE_LEFT}" + CHAR_TABLE_HORIZONTAL * (self.text__width + 2) + f"{CHAR_TABLE_MIDDLE_RIGHT }"
|
255
|
+
def map_text__table_top (self): self.text__table_top = f"{CHAR_TABLE_TOP_LEFT }" + CHAR_TABLE_HORIZONTAL * (self.text__width + 2) + f"{CHAR_TABLE_TOP_RIGHT }"
|
256
|
+
|
257
|
+
def map_text__title(self):
|
258
|
+
self.text__title = f"{CHAR_TABLE_VERTICAL} {self.title:{self.text__width}} {CHAR_TABLE_VERTICAL}"
|
259
|
+
|
260
|
+
def map_text__width(self):
|
261
|
+
self.text__width = self.table_width - 4
|
262
|
+
# if self.table_width > 3: # there is no use case that that needs this check
|
263
|
+
# self.text__width = self.table_width - 4
|
264
|
+
# else:
|
265
|
+
# self.text__width = 0
|
266
|
+
|
267
|
+
def map_texts(self):
|
268
|
+
self.fix_table ()
|
269
|
+
self.map_headers_size ()
|
270
|
+
self.map_text__headers ()
|
271
|
+
self.map_rows_texts ()
|
272
|
+
self.map_table_width ()
|
273
|
+
self.map_text__width ()
|
274
|
+
self.map_text__footer ()
|
275
|
+
self.map_text__title ()
|
276
|
+
self.map_text__table_bottom ()
|
277
|
+
self.map_text__table_middle ()
|
278
|
+
self.map_text__table_top ()
|
279
|
+
self.map_text__all ()
|
280
|
+
|
281
|
+
|
282
|
+
def print(self, data=None, order=None):
|
283
|
+
if data:
|
284
|
+
self.add_data(data)
|
285
|
+
if order:
|
286
|
+
self.reorder_columns(order)
|
287
|
+
print()
|
288
|
+
self.map_texts()
|
289
|
+
for text in self.text__all:
|
290
|
+
print(text)
|
291
|
+
return self
|
292
|
+
|
293
|
+
def should_show_header(self, header):
|
294
|
+
if self.headers_to_hide:
|
295
|
+
if type(header) is int:
|
296
|
+
header_name = self.headers_by_index[header]
|
297
|
+
else:
|
298
|
+
header_name = str(header)
|
299
|
+
return header_name not in self.headers_to_hide
|
300
|
+
return True
|
301
|
+
|
302
|
+
def remove_columns(self,column_names):
|
303
|
+
if type (column_names) is str:
|
304
|
+
column_names = [column_names]
|
305
|
+
if type(column_names) is list:
|
306
|
+
for column_name in column_names:
|
307
|
+
if column_name in self.headers:
|
308
|
+
column_index = self.headers.index(column_name)
|
309
|
+
del self.headers[column_index]
|
310
|
+
for row in self.rows:
|
311
|
+
del row[column_index]
|
312
|
+
return self
|
313
|
+
|
314
|
+
def reorder_columns(self, new_order: list):
|
315
|
+
if set(new_order) != set(self.headers): # Check if the new_order list has the same headers as the current table
|
316
|
+
missing = set(self.headers) - set(new_order) or {}
|
317
|
+
extra = set(new_order) - set(self.headers) or {}
|
318
|
+
raise ValueError("New order must contain the same headers as the current table.\n"
|
319
|
+
f" - Missing headers: {missing}\n"
|
320
|
+
f" - Extra headers: {extra}")
|
321
|
+
|
322
|
+
index_map = {old_header: new_order.index(old_header) for old_header in self.headers} # Create a mapping from old index to new index
|
323
|
+
new_rows = [] # Reorder each row according to the new header order
|
324
|
+
for row in self.rows:
|
325
|
+
new_row = [None] * len(row) # Initialize a new row with placeholders
|
326
|
+
for old_index, cell in enumerate(row):
|
327
|
+
new_index = index_map[self.headers[old_index]]
|
328
|
+
new_row[new_index] = cell
|
329
|
+
new_rows.append(new_row)
|
330
|
+
|
331
|
+
self.headers = list(new_order) # Reorder the headers
|
332
|
+
self.rows = new_rows # Reorder the rows
|
333
|
+
return self
|
334
|
+
|
335
|
+
|
336
|
+
def set_footer(self, footer):
|
337
|
+
self.footer = footer
|
338
|
+
return self
|
339
|
+
|
340
|
+
def set_headers(self, headers):
|
341
|
+
self.headers = headers
|
342
|
+
return self
|
343
|
+
|
344
|
+
def set_order(self, *new_order):
|
345
|
+
return self.reorder_columns(new_order)
|
346
|
+
|
347
|
+
def set_title(self, title):
|
348
|
+
self.title = title
|
349
|
+
return self
|
350
|
+
|
351
|
+
def to_csv(self):
|
352
|
+
csv_content = ','.join(self.to_csv__escape_cell(header) for header in self.headers) + '\n' # Create a CSV string from the headers and rows
|
353
|
+
for row in self.rows:
|
354
|
+
csv_content += ','.join(self.to_csv__escape_cell(cell) if cell is not None else '' for cell in row) + '\n'
|
355
|
+
return csv_content
|
356
|
+
|
357
|
+
def to_csv__escape_cell(self, cell):
|
358
|
+
if cell and any(c in cell for c in [',', '"', '\n']):
|
359
|
+
cell = cell.replace('"', '""') # Escape double quotes
|
360
|
+
cell = cell.replace('\n', '\\n') # escape new lines
|
361
|
+
return f'"{cell}"' # Enclose the cell in double quotes
|
362
|
+
return cell
|
363
|
+
|
364
|
+
def to_dict(self):
|
365
|
+
table_dict = {header: [] for header in self.headers} # Initialize the dictionary with empty lists for each header
|
366
|
+
for row in self.rows: # Iterate over each row and append the cell to the corresponding header's list
|
367
|
+
for header, cell in zip(self.headers, row):
|
368
|
+
table_dict[header].append(cell)
|
369
|
+
return table_dict
|
@@ -0,0 +1,45 @@
|
|
1
|
+
import sys
|
2
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
3
|
+
from osbot_utils.helpers.Print_Table import Print_Table
|
4
|
+
from osbot_utils.utils.Call_Stack import Frame_Data, Call_Stack
|
5
|
+
|
6
|
+
|
7
|
+
class Python_Audit(Kwargs_To_Self):
|
8
|
+
audit_events : list
|
9
|
+
frame_depth : int = 10
|
10
|
+
|
11
|
+
def hook_callback(self, event, args):
|
12
|
+
if event != 'sys._getframe': # since sys._getframe will trigger an event (and cause a recursive loop) we have to ignore it
|
13
|
+
frame = sys._getframe().f_back
|
14
|
+
self.audit_events.append((event, args,frame))
|
15
|
+
|
16
|
+
def data(self):
|
17
|
+
data = []
|
18
|
+
for index, item in enumerate(self.audit_events):
|
19
|
+
(event, args, frame) = item
|
20
|
+
call_stack = Call_Stack(max_depth=self.frame_depth)
|
21
|
+
call_stack.capture_frame(frame)
|
22
|
+
data.append({'index':index, 'event': event, 'args': args, 'stack': call_stack.stats()})
|
23
|
+
return data
|
24
|
+
|
25
|
+
def start(self):
|
26
|
+
sys.addaudithook(self.hook_callback)
|
27
|
+
return self
|
28
|
+
|
29
|
+
def events(self):
|
30
|
+
return self.audit_events
|
31
|
+
|
32
|
+
def events_by_type(self):
|
33
|
+
events_by_type = {}
|
34
|
+
for event, args, stack in self.audit_events:
|
35
|
+
events_by_type[event] = events_by_type.get(event, 0) + 1
|
36
|
+
return events_by_type
|
37
|
+
|
38
|
+
def print(self):
|
39
|
+
with Print_Table() as _:
|
40
|
+
_.add_data(self.data())
|
41
|
+
_.set_order('index', 'event', 'args', 'stack')
|
42
|
+
_.print()
|
43
|
+
|
44
|
+
def size(self):
|
45
|
+
return len(self.events)
|
@@ -0,0 +1,27 @@
|
|
1
|
+
import random
|
2
|
+
|
3
|
+
from osbot_utils.utils.Misc import random_int
|
4
|
+
|
5
|
+
DEFAULT_VALUE__RANDOM_SEED = 42
|
6
|
+
|
7
|
+
class Random_Seed:
|
8
|
+
def __init__(self, seed=DEFAULT_VALUE__RANDOM_SEED, enabled=True):
|
9
|
+
self.enabled = enabled
|
10
|
+
self.seed = seed
|
11
|
+
|
12
|
+
def __enter__(self):
|
13
|
+
if self.enabled:
|
14
|
+
random.seed(self.seed)
|
15
|
+
return self
|
16
|
+
|
17
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
18
|
+
if self.enabled:
|
19
|
+
random.seed(None)
|
20
|
+
|
21
|
+
def next_int(self, **kwargs):
|
22
|
+
return random_int(**kwargs)
|
23
|
+
|
24
|
+
def next_ints(self, count):
|
25
|
+
ints = (self.next_int() for i in range(count))
|
26
|
+
return ints
|
27
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
from osbot_utils.context_managers.capture_duration import capture_duration
|
2
|
+
from osbot_utils.helpers.SSH import SSH
|
3
|
+
from osbot_utils.testing.Temp_Zip import Temp_Zip
|
4
|
+
from osbot_utils.utils.Dev import pprint
|
5
|
+
from osbot_utils.utils.Files import file_exists, file_not_exists, file_name
|
6
|
+
from osbot_utils.utils.Process import start_process
|
7
|
+
from osbot_utils.utils.Status import status_error
|
8
|
+
from osbot_utils.utils.Zip import zip_folder
|
9
|
+
|
10
|
+
|
11
|
+
class SCP(SSH):
|
12
|
+
|
13
|
+
def copy_file_to_host(self, local_file, host_file=None):
|
14
|
+
if file_not_exists(local_file):
|
15
|
+
return status_error(error="in copy_file_to_host, local_file provided doesn't exist in current host", data={'local_file':local_file})
|
16
|
+
if host_file is None:
|
17
|
+
host_file = file_name(local_file)
|
18
|
+
scp_args = self.execute_ssh_args()
|
19
|
+
scp_args += [local_file]
|
20
|
+
scp_args += [f'{self.execute_command_target_host()}:{host_file}']
|
21
|
+
return self.execute_scp_command__return_stderr(scp_args)
|
22
|
+
|
23
|
+
def copy_file_from_host(self, host_file, local_file):
|
24
|
+
scp_args = self.execute_ssh_args()
|
25
|
+
scp_args += [f'{self.execute_command_target_host()}:{host_file}']
|
26
|
+
scp_args += [local_file]
|
27
|
+
return self.execute_scp_command__return_stderr(scp_args)
|
28
|
+
|
29
|
+
|
30
|
+
def copy_folder_as_zip_to_host(self, local_folder, unzip_to_folder):
|
31
|
+
if file_not_exists(local_folder):
|
32
|
+
return status_error(error="in copy_folder_as_zip_to_host, local_folder provided doesn't exist in current host", data={'local_folder':local_folder})
|
33
|
+
with Temp_Zip(target=local_folder) as temp_zip:
|
34
|
+
host_file = temp_zip.file_name()
|
35
|
+
kwargs = dict(local_file = temp_zip.path(),
|
36
|
+
host_file = host_file )
|
37
|
+
self.mkdir(unzip_to_folder)
|
38
|
+
self.copy_file_to_host(**kwargs)
|
39
|
+
command = f'unzip {host_file} -d {unzip_to_folder}'
|
40
|
+
self.execute_command(command)
|
41
|
+
self.rm(host_file)
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
|
46
|
+
def execute_scp_command(self, scp_args):
|
47
|
+
if self.ssh_host and self.ssh_key_file and self.ssh_key_user and scp_args:
|
48
|
+
with capture_duration() as duration:
|
49
|
+
result = start_process("scp", scp_args) # execute scp command using subprocess.run(...)
|
50
|
+
result['duration'] = duration.data()
|
51
|
+
return result
|
52
|
+
return status_error(error='in copy_file not all required vars were setup')
|
53
|
+
|
54
|
+
def execute_scp_command__return_stdout(self, scp_args):
|
55
|
+
return self.execute_scp_command(scp_args).get('stdout').strip()
|
56
|
+
|
57
|
+
def execute_scp_command__return_stderr(self, scp_args):
|
58
|
+
return self.execute_scp_command(scp_args).get('stderr').strip()
|
@@ -0,0 +1,151 @@
|
|
1
|
+
from decimal import Decimal
|
2
|
+
|
3
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
4
|
+
from osbot_utils.context_managers.capture_duration import capture_duration
|
5
|
+
from osbot_utils.decorators.lists.group_by import group_by
|
6
|
+
from osbot_utils.decorators.lists.index_by import index_by
|
7
|
+
from osbot_utils.utils.Dev import pprint
|
8
|
+
from osbot_utils.utils.Misc import timestamp_utc_now
|
9
|
+
from osbot_utils.utils.Process import start_process
|
10
|
+
from osbot_utils.utils.Status import status_error
|
11
|
+
|
12
|
+
class SSH(Kwargs_To_Self):
|
13
|
+
ssh_host : str
|
14
|
+
ssh_key_file : str
|
15
|
+
ssh_key_user : str
|
16
|
+
strict_host_check : bool = False
|
17
|
+
|
18
|
+
def exec(self, command):
|
19
|
+
return self.execute_command__return_stdout(command)
|
20
|
+
|
21
|
+
def execute_command(self, command):
|
22
|
+
if self.ssh_host and self.ssh_key_file and self.ssh_key_user and command: # todo: add check to see if ssh executable exists (this check can be cached)
|
23
|
+
ssh_args = self.execute_command_args(command)
|
24
|
+
with capture_duration() as duration:
|
25
|
+
result = start_process("ssh", ssh_args) # execute command using subprocess.run(...)
|
26
|
+
result['duration'] = duration.data()
|
27
|
+
return result
|
28
|
+
return status_error(error='in execute_command not all required vars were setup')
|
29
|
+
|
30
|
+
def execute_ssh_args(self, command=None):
|
31
|
+
ssh_args = []
|
32
|
+
if self.strict_host_check is False:
|
33
|
+
ssh_args += ['-o',
|
34
|
+
'StrictHostKeyChecking=no'] # todo: add support for updating the local hosts file so that we dont need to do this that often
|
35
|
+
if self.ssh_key_file:
|
36
|
+
ssh_args += ['-i', self.ssh_key_file]
|
37
|
+
return ssh_args
|
38
|
+
|
39
|
+
def execute_command_args(self, command=None):
|
40
|
+
ssh_args = self.execute_ssh_args()
|
41
|
+
if self.ssh_host:
|
42
|
+
ssh_args += [self.execute_command_target_host()]
|
43
|
+
if command:
|
44
|
+
ssh_args += [command]
|
45
|
+
return ssh_args
|
46
|
+
|
47
|
+
def execute_command_target_host(self):
|
48
|
+
if self.ssh_key_user:
|
49
|
+
return f'{self.ssh_key_user}@{self.ssh_host}'
|
50
|
+
else:
|
51
|
+
return f'{self.ssh_host}'
|
52
|
+
|
53
|
+
def execute_command__return_stdout(self, command):
|
54
|
+
return self.execute_command(command).get('stdout').strip()
|
55
|
+
|
56
|
+
def execute_command__return_stderr(self, command):
|
57
|
+
return self.execute_command(command).get('stderr').strip()
|
58
|
+
|
59
|
+
@index_by
|
60
|
+
@group_by
|
61
|
+
def execute_command__return_dict(self, command):
|
62
|
+
stdout = self.execute_command(command).get('stdout').strip()
|
63
|
+
return self.parse_stdout_to_dict(stdout)
|
64
|
+
|
65
|
+
# helpers for common linux methods
|
66
|
+
|
67
|
+
def cat(self, path=''):
|
68
|
+
command = f'cat {path}'
|
69
|
+
return self.execute_command__return_stdout(command)
|
70
|
+
|
71
|
+
@index_by
|
72
|
+
def disk_space(self):
|
73
|
+
command = "df -h"
|
74
|
+
stdout = self.execute_command__return_stdout(command)
|
75
|
+
stdout_disk_space = stdout.replace('Mounted on', 'Mounted_on') # todo, find a better way to do this
|
76
|
+
disk_space = self.parse_stdout_to_dict(stdout_disk_space)
|
77
|
+
return disk_space
|
78
|
+
|
79
|
+
def find(self, path=''):
|
80
|
+
command = f'find {path}'
|
81
|
+
return self.execute_command__return_stdout(command)
|
82
|
+
|
83
|
+
def ls(self, path=''):
|
84
|
+
command = f'ls {path}'
|
85
|
+
ls_raw = self.execute_command__return_stdout(command)
|
86
|
+
return ls_raw.splitlines()
|
87
|
+
|
88
|
+
def mkdir(self, folder):
|
89
|
+
command = f'mkdir -p {folder}'
|
90
|
+
return self.execute_command__return_stdout(command)
|
91
|
+
|
92
|
+
def memory_usage(self):
|
93
|
+
command = "free -h"
|
94
|
+
memory_usage_raw = self.execute_command__return_stdout(command) # todo: add fix for data parsing issue
|
95
|
+
return memory_usage_raw.splitlines()
|
96
|
+
|
97
|
+
def rm(self, path=''):
|
98
|
+
command = f'rm {path}'
|
99
|
+
return self.execute_command__return_stderr(command)
|
100
|
+
|
101
|
+
def running_processes(self,**kwargs):
|
102
|
+
command = "ps aux"
|
103
|
+
return self.execute_command__return_dict(command, **kwargs)
|
104
|
+
|
105
|
+
def system_uptime(self):
|
106
|
+
command = "uptime"
|
107
|
+
uptime_raw = self.execute_command__return_stdout(command)
|
108
|
+
return uptime_raw.strip()
|
109
|
+
|
110
|
+
def uname(self):
|
111
|
+
return self.execute_command__return_stdout('uname')
|
112
|
+
|
113
|
+
def parse_stdout_to_dict(self, stdout):
|
114
|
+
lines = stdout.splitlines()
|
115
|
+
headers = lines[0].split()
|
116
|
+
result = []
|
117
|
+
|
118
|
+
for line in lines[1:]: # Split each line into parts based on whitespace
|
119
|
+
parts = line.split() # Combine the parts with headers to create a dictionary
|
120
|
+
entry = {headers[i]: parts[i] for i in range(len(headers))}
|
121
|
+
result.append(entry)
|
122
|
+
|
123
|
+
return result
|
124
|
+
|
125
|
+
def which(self, target):
|
126
|
+
command = f'which {target}' # todo: security-vuln: add protection against code injection
|
127
|
+
return self.execute_command__return_stdout(command)
|
128
|
+
|
129
|
+
def whoami(self):
|
130
|
+
command = f'whoami' # todo: security-vuln: add protection against code injection
|
131
|
+
return self.execute_command__return_stdout(command)
|
132
|
+
|
133
|
+
# print helpers
|
134
|
+
def print_ls(self, path=''):
|
135
|
+
pprint(self.ls(path))
|
136
|
+
return self
|
137
|
+
|
138
|
+
def print_exec(self, command=''):
|
139
|
+
pprint(self.exec(command))
|
140
|
+
return self
|
141
|
+
# def ifconfig(self):
|
142
|
+
# command = "export PATH=$PATH:/sbin && ifconfig" # todo add example with PATH modification
|
143
|
+
# return self.execute_command__return_stdout(command)
|
144
|
+
|
145
|
+
# def ifconfig(self): # todo add command to execute in separate bash (see when it is needed)
|
146
|
+
# command = "bash -l -c 'ifconfig'"
|
147
|
+
# return self.execute_command__return_stdout(command)
|
148
|
+
# if port_forward: # todo: add support for port forward (this will need async execution)
|
149
|
+
# local_port = port_forward.get('local_port' )
|
150
|
+
# remote_ip = port_forward.get('remote_ip' )
|
151
|
+
# remote_port = port_forward.get('remote_port')
|
@@ -0,0 +1,16 @@
|
|
1
|
+
class Type_Registry:
|
2
|
+
|
3
|
+
def __init__(self):
|
4
|
+
self.types = {}
|
5
|
+
|
6
|
+
def register(self, type_key, type):
|
7
|
+
self.types[type_key] = type
|
8
|
+
|
9
|
+
def resolve(self, type_key):
|
10
|
+
return self.types.get(type_key)
|
11
|
+
|
12
|
+
def resolve_key(self, value):
|
13
|
+
return value
|
14
|
+
|
15
|
+
type_registry = Type_Registry()
|
16
|
+
|
File without changes
|