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,114 @@
1
+ import random
2
+
3
+ from osbot_utils.utils.Misc import random_string
4
+
5
+ from osbot_utils.utils.Files import path_combine, temp_folder_current, safe_file_name, folder_exists, folder_create, \
6
+ folder_delete_recursively, files_list, file_create, folder_files, folders_recursive, files_find, files_recursive, \
7
+ temp_file_in_folder, create_folder, filter_parent_folder
8
+ from osbot_utils.utils.Zip import zip_folder
9
+
10
+
11
+ class Temp_Folder:
12
+
13
+ def __init__(self, folder_name=None, parent_folder=None, temp_prefix='', delete_on_exit=True, temp_files_to_add=0):
14
+ if type(parent_folder) is Temp_Folder:
15
+ parent_folder = parent_folder.path()
16
+ self.folder_name = folder_name or f"temp_folder_{random_string(prefix=temp_prefix)}"
17
+ self.parent_folder = parent_folder or temp_folder_current()
18
+ self.full_path = path_combine(self.parent_folder, safe_file_name(self.folder_name))
19
+ self.delete_on_exit = delete_on_exit
20
+ self.temp_files_to_add = temp_files_to_add
21
+
22
+
23
+ def __enter__(self):
24
+ folder_create(self.full_path)
25
+ self.add_temp_files_and_folders(max_total_files=self.temp_files_to_add)
26
+ return self
27
+
28
+ def __exit__(self, type, value, traceback):
29
+ if self.delete_on_exit:
30
+ folder_delete_recursively(self.full_path)
31
+
32
+ def __repr__(self):
33
+ return f"<Temp_Folder: {self.full_path}>"
34
+
35
+ def __str__(self):
36
+ return self.full_path
37
+
38
+ def add_temp_files(self, count=0):
39
+ if count is None: count = 1
40
+ for i in range(count):
41
+ self.add_file()
42
+
43
+ def add_temp_files_and_folders(self, target_folder=None, max_depth_param=5, max_files_per_folder=4, max_total_files=20, current_depth=0):
44
+ if max_total_files <=0: return max_total_files
45
+ if target_folder is None: target_folder = self.full_path
46
+
47
+ # Base case: if max_depth_param is 0 or we've reached max_total_files, we stop
48
+ if max_depth_param == 0 or max_total_files <= 0:
49
+ return max_total_files
50
+
51
+ # Randomly decide the number of subfolders and files for this folder
52
+ num_subfolders = random.randint(1, max_depth_param)
53
+ num_files = random.randint(1, min(max_files_per_folder, max_total_files))
54
+ #print(f'Creating {num_subfolders} subfolders and {num_files} files in {target_folder}')
55
+ # Create the random files
56
+ for _ in range(num_files):
57
+ temp_file_in_folder(target_folder,prefix= f'temp_file__{max_total_files}')
58
+ max_total_files -= 1
59
+ if max_total_files <= 0:
60
+ return max_total_files
61
+
62
+ current_depth +=1
63
+ # Recursively create subfolders and their contents
64
+ for _ in range(0, num_subfolders):
65
+ subfolder_name = f"_[{current_depth}]_temp_folder_{str(random.randint(0, 512))}_"
66
+ subfolder_path = path_combine(target_folder, subfolder_name)
67
+ create_folder(subfolder_path)
68
+
69
+ # Recursive call with decremented depth and updated max_total_files
70
+ max_total_files = self.add_temp_files_and_folders(subfolder_path, max_depth_param - 1, max_files_per_folder, max_total_files, current_depth=current_depth)
71
+
72
+ if max_total_files <= 0:
73
+ break
74
+
75
+ return max_total_files
76
+
77
+ def add_file(self, file_name=None, contents=None):
78
+ if file_name is None: file_name = f"temp_file_{random_string()}.txt"
79
+ if contents is None: contents = random_string()
80
+ file_path = path_combine(self.full_path, safe_file_name(file_name))
81
+ return file_create(file_path, contents)
82
+
83
+ def add_folder(self, name=None):
84
+ if name is None: name = f"temp_folder_{random_string()}"
85
+ new_folder = path_combine(self.path(), safe_file_name(name))
86
+ return folder_create(new_folder)
87
+
88
+
89
+ def exists(self):
90
+ return folder_exists(self.full_path)
91
+
92
+ def path(self):
93
+ return self.full_path
94
+
95
+ def files(self, show_parent_folder=False, include_folders=False):
96
+ all_files = files_recursive(self.path(), include_folders=include_folders)
97
+ if show_parent_folder:
98
+ return all_files
99
+ return filter_parent_folder(all_files, self.path())
100
+
101
+ def files_and_folders(self, show_parent_folder=False):
102
+ all_files_and_folders = files_recursive(self.path(), include_folders=True)
103
+ if show_parent_folder:
104
+ return all_files_and_folders
105
+ return filter_parent_folder(all_files_and_folders, self.path())
106
+
107
+ def folders(self, show_parent_folder=False):
108
+ all_folders = folders_recursive(self.path())
109
+ if show_parent_folder:
110
+ return all_folders
111
+ return filter_parent_folder(all_folders, self.path())
112
+
113
+ def zip(self):
114
+ return zip_folder(self.path())
@@ -0,0 +1,13 @@
1
+ import sys
2
+
3
+
4
+ class Temp_Sys_Path:
5
+ def __init__(self, path):
6
+ self.path = path
7
+
8
+ def __enter__(self):
9
+ sys.path.append(self.path)
10
+ return self
11
+
12
+ def __exit__(self, exc_type, exc_val, exc_tb):
13
+ sys.path.remove(self.path)
@@ -0,0 +1,83 @@
1
+ from contextlib import contextmanager
2
+ from functools import partial
3
+ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
4
+ from threading import Thread
5
+ from urllib.parse import urljoin
6
+
7
+ from osbot_utils.utils.Files import file_create, path_combine, temp_filename, file_create_all_parent_folders
8
+
9
+ from osbot_utils.utils.Misc import random_port, random_string
10
+
11
+ from osbot_utils.utils.Http import port_is_open, GET
12
+
13
+
14
+ class Temp_Web_Server:
15
+ server : ThreadingHTTPServer
16
+ server_thread : Thread
17
+
18
+ def __init__(self, host: str = None, port: int = None, root_folder: str = None, server_name = None, http_handler = None, wait_for_stop=False):
19
+ self.host = host or "127.0.0.1"
20
+ self.port = port or random_port()
21
+ self.root_folder = root_folder or "."
22
+ self.server_name = server_name or "Temp_Web_Server"
23
+ self.http_handler = http_handler or SimpleHTTPRequestHandler
24
+ self.wait_for_stop = wait_for_stop
25
+
26
+ def __enter__(self):
27
+ self.start()
28
+ return self
29
+
30
+ def __exit__(self, exc_type, exc_val, exc_tb):
31
+ self.stop()
32
+
33
+ def add_file(self, relative_file_path=None, file_contents=None):
34
+ if relative_file_path is None:
35
+ relative_file_path = temp_filename()
36
+ if file_contents is None:
37
+ file_contents = random_string()
38
+ full_path = path_combine(self.root_folder, relative_file_path) # todo: fix the path transversal vulnerability that exists in this function #security
39
+ file_create_all_parent_folders(full_path)
40
+ file_create(path=full_path, contents=file_contents)
41
+ return full_path
42
+
43
+ def GET(self, path=''):
44
+ url = self.url(path)
45
+ try:
46
+ return GET(url)
47
+ except Exception as error:
48
+ print(error) # todo: add support for using logging
49
+ return None
50
+
51
+ def GET_contains(self, content, path=''):
52
+ page_html = self.GET(path=path)
53
+ if type(content) is list:
54
+ for item in content:
55
+ if item not in page_html:
56
+ return False
57
+ return True
58
+ return content in page_html
59
+
60
+ def server_port_open(self):
61
+ return port_is_open(host=self.host, port=self.port)
62
+
63
+ def stop(self):
64
+ self.server.server_close()
65
+ if self.wait_for_stop:
66
+ self.server.shutdown()
67
+ self.server_thread.join()
68
+ else:
69
+ self.server._BaseServer__shutdown_request = True # simulate what happens inside self.server.shutdown()
70
+
71
+ def start(self):
72
+ if self.http_handler is SimpleHTTPRequestHandler:
73
+ handler_config = partial(self.http_handler, directory=self.root_folder)
74
+ else:
75
+ handler_config = partial(self.http_handler)
76
+ self.server = ThreadingHTTPServer((self.host, self.port), handler_config)
77
+ self.server_thread = Thread(target=self.server.serve_forever, name=self.server_name)
78
+ self.server_thread.start()
79
+
80
+ def url(self,path=''):
81
+ base_url = f"http://{self.host}:{self.port}"
82
+ url = urljoin(base_url, path)
83
+ return url
@@ -0,0 +1,45 @@
1
+ from osbot_utils.testing.Temp_Folder import Temp_Folder
2
+ from osbot_utils.utils.Files import Files, is_folder, file_exists, file_name
3
+ from osbot_utils.utils.Zip import zip_folder, zip_file_list
4
+
5
+
6
+ class Temp_Zip():
7
+ def __init__(self, target=None, target_zip_file=None, delete_zip_file=True):
8
+ if type(target) is Temp_Folder:
9
+ target = target.path()
10
+ self.target = target
11
+ self.delete_zip_file = delete_zip_file
12
+ self.target_zip_file = target_zip_file
13
+ self.zip_file = None
14
+ self.zip_bytes = None
15
+ self.target_zipped = False
16
+
17
+ def __enter__(self):
18
+ if is_folder(self.target):
19
+ self.zip_file = zip_folder(self.target)
20
+ self.target_zipped = True
21
+ return self
22
+
23
+ def __exit__(self, type, value, traceback):
24
+ if Files.exists(self.zip_file) and self.delete_zip_file:
25
+ Files.delete(self.zip_file)
26
+
27
+ def __repr__(self):
28
+ return f"<Temp_Zip: {self.path()}>"
29
+
30
+ def file_name(self):
31
+ return file_name(self.zip_file)
32
+
33
+ def path(self):
34
+ return self.zip_file
35
+
36
+ def files(self):
37
+ return zip_file_list(self.zip_file)
38
+
39
+ def print_path(self):
40
+ print()
41
+ print(self.path())
42
+ return self
43
+
44
+ def zip_file_exists(self):
45
+ return file_exists(self.zip_file)
@@ -0,0 +1,90 @@
1
+ import io
2
+
3
+ from osbot_utils.testing.Temp_File import Temp_File
4
+ from osbot_utils.testing.Temp_Folder import Temp_Folder
5
+ from osbot_utils.utils.Files import is_file, is_folder, files_recursive, filter_parent_folder, temp_file
6
+ from osbot_utils.utils.Zip import zip_files_to_bytes, zip_bytes_file_list, zip_bytes_add_file, zip_bytes_get_file
7
+
8
+
9
+ class Temp_Zip_In_Memory:
10
+
11
+ def __init__(self, targets=None, targets_as_bytes=None):
12
+ self.targets = targets or []
13
+ self.targets_as_content = targets_as_bytes or []
14
+ self.root_folder = None
15
+
16
+ def __enter__(self):
17
+ return self
18
+
19
+ def __exit__(self, type, value, traceback):
20
+ pass
21
+
22
+ def add_file(self, file, root_folder=None):
23
+ if type(file) is Temp_File:
24
+ file = file.path()
25
+ if is_file(file):
26
+ self.add_target(file, root_folder)
27
+ return self
28
+
29
+ def add_file_from_content(self, file_path, file_contents):
30
+ self.targets_as_content.append({'file_path': file_path, 'file_contents': file_contents})
31
+ return self
32
+
33
+ def add_folder(self, folder, root_folder=None):
34
+ if type(folder) is Temp_Folder:
35
+ folder = folder.path()
36
+ if is_folder(folder):
37
+ self.add_target(folder, root_folder)
38
+ return self
39
+
40
+ def add_target(self, target, root_folder=None):
41
+ if target:
42
+ self.targets.append({'target': target, 'root_folder': root_folder})
43
+ return self
44
+
45
+ def create_zip_file(self, target_zip_file=None):
46
+ if target_zip_file is None:
47
+ target_zip_file = temp_file(extension='.zip')
48
+ with open(target_zip_file, 'wb') as f:
49
+ f.write(self.zip_bytes())
50
+ return target_zip_file
51
+
52
+ def set_root_folder(self, root_folder):
53
+ if type(root_folder) is Temp_Folder:
54
+ self.root_folder = root_folder.path()
55
+ else:
56
+ self.root_folder = root_folder
57
+ return self
58
+
59
+ def target_files(self):
60
+ return [entry.get('file') for entry in self.target_files_with_root_folder()]
61
+
62
+ def target_files_with_root_folder(self):
63
+ all_files = []
64
+ for entry in self.targets:
65
+ target = entry.get('target')
66
+ root_folder = entry.get('root_folder') or self.root_folder
67
+ if is_file(target):
68
+ all_files.append({'file': target, 'root_folder': root_folder})
69
+ elif is_folder(target):
70
+ for file in files_recursive(target):
71
+ all_files.append({'file': file, 'root_folder': root_folder})
72
+ return all_files
73
+
74
+ def zip_bytes(self):
75
+ zip_bytes = self.zip_buffer().getvalue()
76
+ for items in self.targets_as_content:
77
+ file_path = items.get('file_path')
78
+ file_contents = items.get('file_contents')
79
+ zip_bytes = zip_bytes_add_file(zip_bytes, file_path, file_contents)
80
+ return zip_bytes
81
+
82
+ def zip_bytes_file_content(self, file_path):
83
+ return zip_bytes_get_file(self.zip_bytes(), file_path)
84
+
85
+ def zip_bytes_files(self):
86
+ return zip_bytes_file_list(self.zip_bytes())
87
+
88
+ def zip_buffer(self):
89
+ targets = self.target_files_with_root_folder()
90
+ return zip_files_to_bytes(targets, root_folder=self.root_folder)
@@ -0,0 +1,34 @@
1
+ import base64
2
+ from unittest import TestCase
3
+ from osbot_utils.utils.Dev import pprint
4
+
5
+
6
+ class Unit_Test(TestCase):
7
+ """Unit test helpers
8
+
9
+ - self.result will be written to the output
10
+ - self.png_data will be saved to '/tmp/unit-test.png'"""
11
+
12
+ def setUp(self):
13
+ self.png_file = '/tmp/unit-test.png'
14
+ self.result = None
15
+ self.png_data = None
16
+
17
+ def tearDown(self):
18
+ if self.result is not None:
19
+ pprint(self.result)
20
+ if self.png_data is not None:
21
+ if type(self.png_data) is bytes:
22
+ with open(self.png_file, "wb") as file:
23
+ file.write(self.png_data)
24
+ pprint(f'Png data with size {len(self.png_data)} saved to {self.png_file}')
25
+ elif type(self.png_data) is str:
26
+ try:
27
+ with open(self.png_file, "wb") as file:
28
+ file.write(base64.decodebytes(self.png_data.encode()))
29
+ pprint(f'Png data with size {len(self.png_data)} saved to {self.png_file}')
30
+ except Exception as error:
31
+ pprint(f'png save error: {error}')
32
+ pprint(self.png_data)
33
+ else:
34
+ pprint(f'Error Png data was not a string: {self.png_data}')
@@ -0,0 +1,30 @@
1
+ from osbot_utils.utils.Files import Files, folder_exists, folder_delete_all, temp_folder, files_recursive
2
+ from osbot_utils.utils.Zip import unzip_file
3
+
4
+
5
+ class Unzip_File:
6
+ def __init__(self, zip_file=None, target_folder=None, delete_target_folder=True):
7
+ self.target_folder = target_folder
8
+ self.zip_file = zip_file
9
+ self.delete_target_folder = delete_target_folder
10
+ self.files_unzipped = False
11
+ self.target_folder_deleted = False
12
+
13
+ def __enter__(self):
14
+ if Files.exists(self.zip_file):
15
+ if self.target_folder is None:
16
+ self.target_folder = temp_folder("unzipped_")
17
+ unzip_file(self.zip_file, self.target_folder)
18
+ self.files_unzipped = True
19
+ return self
20
+
21
+ def __exit__(self, type, value, traceback):
22
+ if folder_exists(self.target_folder) and self.delete_target_folder:
23
+ self.target_folder_deleted = folder_delete_all(self.target_folder)
24
+ #print("\n\ndeleting", self.target_folder)
25
+
26
+ def path(self):
27
+ return self.target_folder
28
+
29
+ def files(self):
30
+ return files_recursive(self.target_folder)
File without changes
@@ -0,0 +1,52 @@
1
+ import re
2
+ import datetime
3
+
4
+ from osbot_utils.utils.Objects import class_name
5
+
6
+
7
+ class Assert:
8
+ def __init__(self ,target):
9
+ self.target = target
10
+
11
+ def is_class(self, name):
12
+ assert class_name(self.target) in name
13
+
14
+ def contains(self, text):
15
+ assert text in self.target
16
+
17
+ def field_is_equal(self, field_name, expected_value=None):
18
+ field_value = self.target.get(field_name)
19
+ assert field_value == expected_value , "{0} != {1}".format(field_value, expected_value)
20
+ return self
21
+
22
+ def is_bigger_than(self, value):
23
+ if type(self.target) is list:
24
+ list_len = len(self.target)
25
+ assert list_len > value , "array with len {0} was not bigger than {1}".format(list_len, value)
26
+ else:
27
+ assert self.target > value , "value {0} was not bigger than {1}".format(self.target, value)
28
+ return self
29
+
30
+ def is_smaller_than(self, value):
31
+ if type(self.target) is list:
32
+ list_len = len(self.target)
33
+ assert list_len < value , "array with len {0} was not smaller than {1}".format(list_len, value)
34
+ else:
35
+ assert self.target < value , "value {0} was not smaller than {1}".format(self.target, value)
36
+ return self
37
+
38
+ def is_equal(self, to):
39
+ assert self.target == to
40
+
41
+ def is_today(self):
42
+ assert type(self.target) == datetime.datetime
43
+ assert str(self.target.date()) == str(datetime.datetime.utcnow().date())
44
+
45
+ def match_regex(self, regex):
46
+ assert re.compile(regex).match(self.target) is not None
47
+
48
+ def size_is(self, to):
49
+ assert len(self.target) == to
50
+
51
+ def regex_not_match(self,regex):
52
+ assert re.compile(regex).match(self.target) is None
@@ -0,0 +1,187 @@
1
+ import linecache
2
+ import sys
3
+ import traceback
4
+ from osbot_utils.helpers.CPrint import CPrint
5
+ from osbot_utils.helpers.Print_Table import Print_Table, CHAR_TABLE_HORIZONTAL, CHAR_TABLE_TOP_LEFT, CHAR_TABLE_VERTICAL, CHAR_TABLE_BOTTOM_LEFT
6
+ from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
7
+ from osbot_utils.utils.Objects import obj_data, class_full_name
8
+
9
+ MODULES_TO_STOP_CAPTURE = ['unittest.case']
10
+ PRINT_STACK_COLOR_THEMES = { 'default' : ('none' , 'none' , 'none' ),
11
+ 'minty' : ('green' , 'grey' , 'cyan' ) ,
12
+ 'meadow' : ('blue' , 'none' ,'green' ) ,
13
+ 'aquamarine' : ('bright_cyan' , 'cyan' ,'blue' ),
14
+ 'autumn' : ('bright_yellow' , 'yellow','red' )}
15
+
16
+ class Frame_Data(Kwargs_To_Self):
17
+ depth : int
18
+ caller_line : str
19
+ method_line : str
20
+ method_name : str
21
+ line_number : int
22
+ local_self : str
23
+ module : str
24
+
25
+ def __repr__(self):
26
+ return f"{self.data()})"
27
+
28
+ def data(self):
29
+ return self.__locals__()
30
+
31
+ class Call_Stack(Kwargs_To_Self):
32
+ capture_locals : bool = False
33
+ cprint : CPrint
34
+ cprint_theme : str = 'meadow'
35
+ frames : list
36
+ max_depth : int = 10
37
+
38
+ __print_order__: list = ['module', 'method_name', 'caller_line', 'method_line', 'local_self', 'line_number', 'depth']
39
+
40
+ def __repr__(self):
41
+ return "Call_Stack"
42
+
43
+ def calls(self):
44
+ calls = []
45
+ for frame in self.frames:
46
+ method_name = frame.method_name
47
+ module = frame.module
48
+ call = f"{module}.{method_name}"
49
+ calls.append(call)
50
+ return calls
51
+
52
+ def calls__source_code(self):
53
+ calls = []
54
+ for frame in self.frames:
55
+ calls.append(frame.caller_line)
56
+ return calls
57
+
58
+
59
+ def capture(self,skip_caller=True):
60
+ current_frame = sys._getframe().f_back
61
+ if skip_caller:
62
+ current_frame = current_frame.f_back
63
+ return self.capture_frame(current_frame)
64
+
65
+ def capture_frame(self, frame):
66
+ depth = 0
67
+ while frame:
68
+ if self.stop_capture(frame, depth):
69
+ break
70
+ new_frame = self.new_frame(frame,depth)
71
+ self.frames.append(new_frame)
72
+ depth += 1
73
+ frame = frame.f_back
74
+ return self
75
+
76
+ def stats(self):
77
+ return { 'depth' : len(self.frames) }
78
+
79
+ def stop_capture(self, frame, depth):
80
+ if frame is None:
81
+ return True
82
+ if depth > self.max_depth:
83
+ return True
84
+ module = frame.f_globals.get("__name__", "")
85
+
86
+ if module in MODULES_TO_STOP_CAPTURE:
87
+ return True
88
+ return False
89
+
90
+ def new_frame(self, frame, depth):
91
+ file_path = frame.f_code.co_filename
92
+ method_name = frame.f_code.co_name
93
+ caller_line_number = frame.f_lineno
94
+ method_line_number = frame.f_code.co_firstlineno
95
+
96
+ caller_line = linecache.getline(file_path, caller_line_number).strip()
97
+ method_line = linecache.getline(file_path, method_line_number).strip() # see if we need to add the code to resolve the function from the decorators
98
+ module = frame.f_globals.get("__name__", "")
99
+ local_self = class_full_name(frame.f_locals.get('self'))
100
+ frame_data = Frame_Data( depth = depth ,
101
+ caller_line = caller_line ,
102
+ method_line = method_line ,
103
+ method_name = method_name ,
104
+ module = module ,
105
+ local_self = local_self ,
106
+ line_number = caller_line_number )
107
+ if self.capture_locals:
108
+ frame_data.locals = frame.f_locals,
109
+ return frame_data
110
+
111
+ def print(self):
112
+ for line in self.stack_lines__calls():
113
+ print(line)
114
+
115
+ def print__source_code(self):
116
+ for line in self.stack_lines__source_code():
117
+ print(line)
118
+
119
+ def stack_lines(self, items):
120
+ self.cprint.lines = []
121
+ self.cprint.auto_print = False
122
+
123
+ if len(items) == 0:
124
+ return []
125
+
126
+ if len(items) == 1:
127
+ self.print_with_color('none', f"{CHAR_TABLE_HORIZONTAL} {items[0]}")
128
+ else:
129
+ color_top, color_middle, color_bottom = self.stack_colors()
130
+ self.print_with_color(color_top, text=f"{CHAR_TABLE_TOP_LEFT} {items[0]}")
131
+ for call in items[1:-1]:
132
+ self.print_with_color(color_middle, text=f"{CHAR_TABLE_VERTICAL} {call}")
133
+ self.print_with_color(color_bottom, text=f"{CHAR_TABLE_BOTTOM_LEFT} {items[-1]}")
134
+ return self.cprint.lines
135
+
136
+ def stack_lines__calls(self):
137
+ calls = self.calls()
138
+ return self.stack_lines(calls)
139
+
140
+ def stack_lines__source_code(self):
141
+ calls_source_code = self.calls__source_code()
142
+ return self.stack_lines(calls_source_code)
143
+
144
+ def stack_colors(self):
145
+ color_themes = PRINT_STACK_COLOR_THEMES
146
+ stack_colors = color_themes.get(self.cprint_theme)
147
+ if not stack_colors:
148
+ stack_colors = color_themes.get('default')
149
+ return stack_colors
150
+
151
+ def print_with_color(self, color_name, text):
152
+ self.cprint.__getattribute__(color_name)(text)
153
+
154
+ def print_table(self,headers_to_hide=None):
155
+ all_data = []
156
+ for frame in self.frames:
157
+ all_data.append(frame.data())
158
+
159
+ with Print_Table() as _:
160
+ _.add_data(all_data)
161
+ _.hide_headers(headers_to_hide)
162
+ _.print(order=self.__print_order__)
163
+
164
+
165
+ # static methods
166
+ # todo: refactor these static methods to be in a separate class and Call_Stack into the helpers folder
167
+
168
+ def call_stack_current_frame(return_caller=True):
169
+ if return_caller:
170
+ return sys._getframe().f_back
171
+ return sys._getframe()
172
+
173
+ def call_stack_format_stack(depth=None):
174
+ return traceback.format_stack(limit=depth)
175
+
176
+ def call_stack_frames(depth=None):
177
+ return traceback.extract_stack(limit=depth)
178
+
179
+ def call_stack_frames_data(depth=None):
180
+ frames_data = []
181
+ for frame in call_stack_frames(depth=depth):
182
+ frames_data.append(obj_data(frame))
183
+ return frames_data
184
+
185
+ def frames_in_threads():
186
+ return sys._current_frames()
187
+
@@ -0,0 +1,32 @@
1
+ import csv
2
+ from io import StringIO
3
+ from osbot_utils.utils.Http import GET
4
+ from osbot_utils.utils.Files import file_open
5
+ from osbot_utils.decorators.lists.group_by import group_by
6
+ from osbot_utils.decorators.lists.index_by import index_by
7
+
8
+
9
+ @index_by
10
+ @group_by
11
+ def load_csv_from_iterable(iterable, delimiter=','):
12
+ csv_reader = csv.DictReader(iterable, delimiter=delimiter)
13
+ return [row for row in csv_reader]
14
+
15
+ @index_by
16
+ @group_by
17
+ def load_csv_from_file(file_path, delimiter=','):
18
+ iterable = file_open(file_path)
19
+ return load_csv_from_iterable(iterable, delimiter=delimiter)
20
+
21
+ @index_by
22
+ @group_by
23
+ def load_csv_from_str(csv_data, delimiter=','):
24
+ iterable = StringIO(csv_data)
25
+ return load_csv_from_iterable(iterable, delimiter=delimiter)
26
+
27
+ @index_by
28
+ @group_by
29
+ def load_csv_from_url(url, headers, delimiter=','):
30
+ csv_data = GET(url=url, headers=headers)
31
+ return load_csv_from_str(csv_data=csv_data, delimiter=delimiter)
32
+