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.
Files changed (260) hide show
  1. osbot_utils/__init__.py +1 -0
  2. osbot_utils/base_classes/Cache_Pickle.py +129 -0
  3. osbot_utils/base_classes/Kwargs_To_Disk.py +27 -0
  4. osbot_utils/base_classes/Kwargs_To_Self.py +308 -0
  5. osbot_utils/base_classes/Type_Safe__List.py +14 -0
  6. osbot_utils/base_classes/__init__.py +0 -0
  7. osbot_utils/context_managers/__init__.py +0 -0
  8. osbot_utils/context_managers/capture_duration.py +33 -0
  9. osbot_utils/decorators/__init__.py +0 -0
  10. osbot_utils/decorators/classes/__init__.py +0 -0
  11. osbot_utils/decorators/classes/singleton.py +9 -0
  12. osbot_utils/decorators/lists/__init__.py +0 -0
  13. osbot_utils/decorators/lists/filter_list.py +12 -0
  14. osbot_utils/decorators/lists/group_by.py +21 -0
  15. osbot_utils/decorators/lists/index_by.py +27 -0
  16. osbot_utils/decorators/methods/__init__.py +0 -0
  17. osbot_utils/decorators/methods/cache.py +19 -0
  18. osbot_utils/decorators/methods/cache_on_function.py +56 -0
  19. osbot_utils/decorators/methods/cache_on_self.py +78 -0
  20. osbot_utils/decorators/methods/cache_on_tmp.py +71 -0
  21. osbot_utils/decorators/methods/capture_exception.py +37 -0
  22. osbot_utils/decorators/methods/capture_status.py +20 -0
  23. osbot_utils/decorators/methods/catch.py +13 -0
  24. osbot_utils/decorators/methods/context.py +11 -0
  25. osbot_utils/decorators/methods/depreciated.py +79 -0
  26. osbot_utils/decorators/methods/function_type_check.py +62 -0
  27. osbot_utils/decorators/methods/obj_as_context.py +6 -0
  28. osbot_utils/decorators/methods/remove_return_value.py +22 -0
  29. osbot_utils/decorators/methods/required_fields.py +19 -0
  30. osbot_utils/fluent/Fluent_Dict.py +19 -0
  31. osbot_utils/fluent/Fluent_List.py +44 -0
  32. osbot_utils/fluent/__init__.py +1 -0
  33. osbot_utils/graphs/__init__.py +0 -0
  34. osbot_utils/graphs/mermaid/Mermaid.py +75 -0
  35. osbot_utils/graphs/mermaid/Mermaid__Edge.py +49 -0
  36. osbot_utils/graphs/mermaid/Mermaid__Graph.py +93 -0
  37. osbot_utils/graphs/mermaid/Mermaid__Node.py +69 -0
  38. osbot_utils/graphs/mermaid/Mermaid__Renderer.py +54 -0
  39. osbot_utils/graphs/mermaid/configs/Mermaid__Edge__Config.py +7 -0
  40. osbot_utils/graphs/mermaid/configs/Mermaid__Node__Config.py +9 -0
  41. osbot_utils/graphs/mermaid/configs/Mermaid__Render__Config.py +7 -0
  42. osbot_utils/graphs/mermaid/examples/Mermaid_Examples__FlowChart.py +98 -0
  43. osbot_utils/graphs/mermaid/models/Mermaid__Diagram_Direction.py +9 -0
  44. osbot_utils/graphs/mermaid/models/Mermaid__Diagram__Type.py +17 -0
  45. osbot_utils/graphs/mermaid/models/Mermaid__Node__Shape.py +30 -0
  46. osbot_utils/graphs/mgraph/MGraph.py +53 -0
  47. osbot_utils/graphs/mgraph/MGraph__Config.py +7 -0
  48. osbot_utils/graphs/mgraph/MGraph__Data.py +139 -0
  49. osbot_utils/graphs/mgraph/MGraph__Edge.py +27 -0
  50. osbot_utils/graphs/mgraph/MGraph__Node.py +33 -0
  51. osbot_utils/graphs/mgraph/MGraph__Random_Graphs.py +27 -0
  52. osbot_utils/graphs/mgraph/MGraph__Serializer.py +43 -0
  53. osbot_utils/graphs/mgraph/MGraphs.py +17 -0
  54. osbot_utils/graphs/mgraph/__init__.py +0 -0
  55. osbot_utils/helpers/CPrint.py +98 -0
  56. osbot_utils/helpers/Dict_To_Attr.py +7 -0
  57. osbot_utils/helpers/Local_Cache.py +111 -0
  58. osbot_utils/helpers/Local_Caches.py +54 -0
  59. osbot_utils/helpers/Print_Table.py +369 -0
  60. osbot_utils/helpers/Python_Audit.py +45 -0
  61. osbot_utils/helpers/Random_Seed.py +27 -0
  62. osbot_utils/helpers/SCP.py +58 -0
  63. osbot_utils/helpers/SSH.py +151 -0
  64. osbot_utils/helpers/Type_Registry.py +16 -0
  65. osbot_utils/helpers/__init__.py +0 -0
  66. osbot_utils/helpers/ast/Ast.py +35 -0
  67. osbot_utils/helpers/ast/Ast_Base.py +124 -0
  68. osbot_utils/helpers/ast/Ast_Data.py +28 -0
  69. osbot_utils/helpers/ast/Ast_Load.py +62 -0
  70. osbot_utils/helpers/ast/Ast_Merge.py +26 -0
  71. osbot_utils/helpers/ast/Ast_Node.py +117 -0
  72. osbot_utils/helpers/ast/Ast_Visit.py +85 -0
  73. osbot_utils/helpers/ast/Call_Tree.py +38 -0
  74. osbot_utils/helpers/ast/__init__.py +145 -0
  75. osbot_utils/helpers/ast/nodes/Ast_Add.py +6 -0
  76. osbot_utils/helpers/ast/nodes/Ast_Alias.py +6 -0
  77. osbot_utils/helpers/ast/nodes/Ast_And.py +6 -0
  78. osbot_utils/helpers/ast/nodes/Ast_Argument.py +7 -0
  79. osbot_utils/helpers/ast/nodes/Ast_Arguments.py +10 -0
  80. osbot_utils/helpers/ast/nodes/Ast_Assert.py +7 -0
  81. osbot_utils/helpers/ast/nodes/Ast_Assign.py +8 -0
  82. osbot_utils/helpers/ast/nodes/Ast_Attribute.py +9 -0
  83. osbot_utils/helpers/ast/nodes/Ast_Aug_Assign.py +9 -0
  84. osbot_utils/helpers/ast/nodes/Ast_Bin_Op.py +8 -0
  85. osbot_utils/helpers/ast/nodes/Ast_Bool_Op.py +7 -0
  86. osbot_utils/helpers/ast/nodes/Ast_Break.py +7 -0
  87. osbot_utils/helpers/ast/nodes/Ast_Call.py +17 -0
  88. osbot_utils/helpers/ast/nodes/Ast_Class_Def.py +9 -0
  89. osbot_utils/helpers/ast/nodes/Ast_Compare.py +9 -0
  90. osbot_utils/helpers/ast/nodes/Ast_Comprehension.py +10 -0
  91. osbot_utils/helpers/ast/nodes/Ast_Constant.py +6 -0
  92. osbot_utils/helpers/ast/nodes/Ast_Continue.py +7 -0
  93. osbot_utils/helpers/ast/nodes/Ast_Dict.py +8 -0
  94. osbot_utils/helpers/ast/nodes/Ast_Eq.py +6 -0
  95. osbot_utils/helpers/ast/nodes/Ast_Except_Handler.py +9 -0
  96. osbot_utils/helpers/ast/nodes/Ast_Expr.py +7 -0
  97. osbot_utils/helpers/ast/nodes/Ast_For.py +10 -0
  98. osbot_utils/helpers/ast/nodes/Ast_Function_Def.py +17 -0
  99. osbot_utils/helpers/ast/nodes/Ast_Generator_Exp.py +8 -0
  100. osbot_utils/helpers/ast/nodes/Ast_Gt.py +7 -0
  101. osbot_utils/helpers/ast/nodes/Ast_GtE.py +7 -0
  102. osbot_utils/helpers/ast/nodes/Ast_If.py +9 -0
  103. osbot_utils/helpers/ast/nodes/Ast_If_Exp.py +9 -0
  104. osbot_utils/helpers/ast/nodes/Ast_Import.py +7 -0
  105. osbot_utils/helpers/ast/nodes/Ast_Import_From.py +7 -0
  106. osbot_utils/helpers/ast/nodes/Ast_In.py +6 -0
  107. osbot_utils/helpers/ast/nodes/Ast_Is.py +6 -0
  108. osbot_utils/helpers/ast/nodes/Ast_Is_Not.py +7 -0
  109. osbot_utils/helpers/ast/nodes/Ast_Keyword.py +8 -0
  110. osbot_utils/helpers/ast/nodes/Ast_Lambda.py +8 -0
  111. osbot_utils/helpers/ast/nodes/Ast_List.py +8 -0
  112. osbot_utils/helpers/ast/nodes/Ast_List_Comp.py +8 -0
  113. osbot_utils/helpers/ast/nodes/Ast_Load.py +6 -0
  114. osbot_utils/helpers/ast/nodes/Ast_Lt.py +7 -0
  115. osbot_utils/helpers/ast/nodes/Ast_LtE.py +7 -0
  116. osbot_utils/helpers/ast/nodes/Ast_Mod.py +6 -0
  117. osbot_utils/helpers/ast/nodes/Ast_Module.py +20 -0
  118. osbot_utils/helpers/ast/nodes/Ast_Mult.py +6 -0
  119. osbot_utils/helpers/ast/nodes/Ast_Name.py +6 -0
  120. osbot_utils/helpers/ast/nodes/Ast_Not.py +7 -0
  121. osbot_utils/helpers/ast/nodes/Ast_Not_Eq.py +7 -0
  122. osbot_utils/helpers/ast/nodes/Ast_Not_In.py +6 -0
  123. osbot_utils/helpers/ast/nodes/Ast_Or.py +7 -0
  124. osbot_utils/helpers/ast/nodes/Ast_Pass.py +7 -0
  125. osbot_utils/helpers/ast/nodes/Ast_Pow.py +7 -0
  126. osbot_utils/helpers/ast/nodes/Ast_Raise.py +8 -0
  127. osbot_utils/helpers/ast/nodes/Ast_Return.py +7 -0
  128. osbot_utils/helpers/ast/nodes/Ast_Set.py +7 -0
  129. osbot_utils/helpers/ast/nodes/Ast_Slice.py +8 -0
  130. osbot_utils/helpers/ast/nodes/Ast_Starred.py +8 -0
  131. osbot_utils/helpers/ast/nodes/Ast_Store.py +7 -0
  132. osbot_utils/helpers/ast/nodes/Ast_Sub.py +7 -0
  133. osbot_utils/helpers/ast/nodes/Ast_Subscript.py +8 -0
  134. osbot_utils/helpers/ast/nodes/Ast_Try.py +9 -0
  135. osbot_utils/helpers/ast/nodes/Ast_Tuple.py +9 -0
  136. osbot_utils/helpers/ast/nodes/Ast_Unary_Op.py +7 -0
  137. osbot_utils/helpers/ast/nodes/Ast_While.py +8 -0
  138. osbot_utils/helpers/ast/nodes/Ast_With.py +7 -0
  139. osbot_utils/helpers/ast/nodes/Ast_With_Item.py +7 -0
  140. osbot_utils/helpers/ast/nodes/Ast_Yield.py +7 -0
  141. osbot_utils/helpers/ast/nodes/__init__.py +0 -0
  142. osbot_utils/helpers/html/Dict_To_Css.py +20 -0
  143. osbot_utils/helpers/html/Dict_To_Html.py +59 -0
  144. osbot_utils/helpers/html/Dict_To_Tags.py +88 -0
  145. osbot_utils/helpers/html/Html_To_Dict.py +75 -0
  146. osbot_utils/helpers/html/Html_To_Tag.py +20 -0
  147. osbot_utils/helpers/html/Tag__Base.py +91 -0
  148. osbot_utils/helpers/html/Tag__Body.py +5 -0
  149. osbot_utils/helpers/html/Tag__Div.py +5 -0
  150. osbot_utils/helpers/html/Tag__H.py +9 -0
  151. osbot_utils/helpers/html/Tag__HR.py +5 -0
  152. osbot_utils/helpers/html/Tag__Head.py +32 -0
  153. osbot_utils/helpers/html/Tag__Html.py +42 -0
  154. osbot_utils/helpers/html/Tag__Link.py +17 -0
  155. osbot_utils/helpers/html/Tag__Style.py +25 -0
  156. osbot_utils/helpers/html/__init__.py +0 -0
  157. osbot_utils/helpers/pubsub/Event__Queue.py +95 -0
  158. osbot_utils/helpers/pubsub/PubSub__Client.py +53 -0
  159. osbot_utils/helpers/pubsub/PubSub__Room.py +13 -0
  160. osbot_utils/helpers/pubsub/PubSub__Server.py +94 -0
  161. osbot_utils/helpers/pubsub/PubSub__Sqlite.py +24 -0
  162. osbot_utils/helpers/pubsub/__init__.py +0 -0
  163. osbot_utils/helpers/pubsub/schemas/Schema__Event.py +15 -0
  164. osbot_utils/helpers/pubsub/schemas/Schema__Event__Connect.py +7 -0
  165. osbot_utils/helpers/pubsub/schemas/Schema__Event__Disconnect.py +7 -0
  166. osbot_utils/helpers/pubsub/schemas/Schema__Event__Join_Room.py +8 -0
  167. osbot_utils/helpers/pubsub/schemas/Schema__Event__Leave_Room.py +8 -0
  168. osbot_utils/helpers/pubsub/schemas/Schema__Event__Message.py +7 -0
  169. osbot_utils/helpers/pubsub/schemas/Schema__PubSub__Client.py +8 -0
  170. osbot_utils/helpers/pubsub/schemas/__init__.py +0 -0
  171. osbot_utils/helpers/sqlite/Capture_Sqlite_Error.py +51 -0
  172. osbot_utils/helpers/sqlite/Sqlite__Cursor.py +87 -0
  173. osbot_utils/helpers/sqlite/Sqlite__Database.py +137 -0
  174. osbot_utils/helpers/sqlite/Sqlite__Field.py +70 -0
  175. osbot_utils/helpers/sqlite/Sqlite__Globals.py +5 -0
  176. osbot_utils/helpers/sqlite/Sqlite__Table.py +293 -0
  177. osbot_utils/helpers/sqlite/Sqlite__Table__Create.py +96 -0
  178. osbot_utils/helpers/sqlite/Temp_Sqlite__Database__Disk.py +17 -0
  179. osbot_utils/helpers/sqlite/Temp_Sqlite__Table.py +23 -0
  180. osbot_utils/helpers/sqlite/__init__.py +0 -0
  181. osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests.py +214 -0
  182. osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests__Patch.py +63 -0
  183. osbot_utils/helpers/sqlite/domains/Sqlite__DB__Files.py +23 -0
  184. osbot_utils/helpers/sqlite/domains/Sqlite__DB__Graph.py +47 -0
  185. osbot_utils/helpers/sqlite/domains/Sqlite__DB__Json.py +83 -0
  186. osbot_utils/helpers/sqlite/domains/Sqlite__DB__Local.py +20 -0
  187. osbot_utils/helpers/sqlite/domains/Sqlite__DB__Requests.py +39 -0
  188. osbot_utils/helpers/sqlite/domains/__init__.py +0 -0
  189. osbot_utils/helpers/sqlite/domains/schemas/Schema__Table__Requests.py +12 -0
  190. osbot_utils/helpers/sqlite/domains/schemas/__init__.py +0 -0
  191. osbot_utils/helpers/sqlite/models/Sqlite__Field__Type.py +37 -0
  192. osbot_utils/helpers/sqlite/models/__init__.py +0 -0
  193. osbot_utils/helpers/sqlite/sample_data/Sqlite__Sample_Data__Chinook.py +116 -0
  194. osbot_utils/helpers/sqlite/sample_data/__init__.py +0 -0
  195. osbot_utils/helpers/sqlite/sql_builder/SQL_Builder.py +159 -0
  196. osbot_utils/helpers/sqlite/sql_builder/SQL_Builder__Select.py +12 -0
  197. osbot_utils/helpers/sqlite/sql_builder/__init__.py +0 -0
  198. osbot_utils/helpers/sqlite/tables/Sqlite__Table__Config.py +63 -0
  199. osbot_utils/helpers/sqlite/tables/Sqlite__Table__Edges.py +46 -0
  200. osbot_utils/helpers/sqlite/tables/Sqlite__Table__Files.py +45 -0
  201. osbot_utils/helpers/sqlite/tables/Sqlite__Table__Nodes.py +52 -0
  202. osbot_utils/helpers/sqlite/tables/__init__.py +0 -0
  203. osbot_utils/helpers/trace/Trace_Call.py +120 -0
  204. osbot_utils/helpers/trace/Trace_Call__Config.py +94 -0
  205. osbot_utils/helpers/trace/Trace_Call__Graph.py +26 -0
  206. osbot_utils/helpers/trace/Trace_Call__Handler.py +215 -0
  207. osbot_utils/helpers/trace/Trace_Call__Print_Lines.py +85 -0
  208. osbot_utils/helpers/trace/Trace_Call__Print_Traces.py +170 -0
  209. osbot_utils/helpers/trace/Trace_Call__Stack.py +166 -0
  210. osbot_utils/helpers/trace/Trace_Call__Stack_Node.py +59 -0
  211. osbot_utils/helpers/trace/Trace_Call__Stats.py +71 -0
  212. osbot_utils/helpers/trace/Trace_Call__View_Model.py +75 -0
  213. osbot_utils/helpers/trace/Trace_Files.py +33 -0
  214. osbot_utils/helpers/trace/__init__.py +0 -0
  215. osbot_utils/testing/Catch.py +54 -0
  216. osbot_utils/testing/Duration.py +69 -0
  217. osbot_utils/testing/Hook_Method.py +118 -0
  218. osbot_utils/testing/Log_To_Queue.py +46 -0
  219. osbot_utils/testing/Log_To_String.py +37 -0
  220. osbot_utils/testing/Logging.py +81 -0
  221. osbot_utils/testing/Patch_Print.py +52 -0
  222. osbot_utils/testing/Profiler.py +89 -0
  223. osbot_utils/testing/Stderr.py +19 -0
  224. osbot_utils/testing/Stdout.py +19 -0
  225. osbot_utils/testing/Temp_File.py +46 -0
  226. osbot_utils/testing/Temp_Folder.py +114 -0
  227. osbot_utils/testing/Temp_Sys_Path.py +13 -0
  228. osbot_utils/testing/Temp_Web_Server.py +83 -0
  229. osbot_utils/testing/Temp_Zip.py +45 -0
  230. osbot_utils/testing/Temp_Zip_In_Memory.py +90 -0
  231. osbot_utils/testing/Unit_Test.py +34 -0
  232. osbot_utils/testing/Unzip_File.py +30 -0
  233. osbot_utils/testing/__init__.py +0 -0
  234. osbot_utils/utils/Assert.py +52 -0
  235. osbot_utils/utils/Call_Stack.py +187 -0
  236. osbot_utils/utils/Csv.py +32 -0
  237. osbot_utils/utils/Dev.py +47 -0
  238. osbot_utils/utils/Exceptions.py +7 -0
  239. osbot_utils/utils/Files.py +528 -0
  240. osbot_utils/utils/Functions.py +113 -0
  241. osbot_utils/utils/Http.py +136 -0
  242. osbot_utils/utils/Int.py +6 -0
  243. osbot_utils/utils/Json.py +171 -0
  244. osbot_utils/utils/Json_Cache.py +59 -0
  245. osbot_utils/utils/Lists.py +198 -0
  246. osbot_utils/utils/Misc.py +496 -0
  247. osbot_utils/utils/Objects.py +341 -0
  248. osbot_utils/utils/Png.py +29 -0
  249. osbot_utils/utils/Process.py +73 -0
  250. osbot_utils/utils/Python_Logger.py +301 -0
  251. osbot_utils/utils/Status.py +79 -0
  252. osbot_utils/utils/Str.py +63 -0
  253. osbot_utils/utils/Version.py +16 -0
  254. osbot_utils/utils/Zip.py +97 -0
  255. osbot_utils/utils/__init__.py +16 -0
  256. osbot_utils/version +1 -0
  257. osbot_utils-1.7.7.dist-info/LICENSE +201 -0
  258. osbot_utils-1.7.7.dist-info/METADATA +46 -0
  259. osbot_utils-1.7.7.dist-info/RECORD +260 -0
  260. 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