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 @@
1
+ path=__path__[0]
@@ -0,0 +1,129 @@
1
+ import os
2
+ from functools import wraps
3
+
4
+ from osbot_utils.utils.Files import path_combine, folder_create, temp_folder_current, file_exists, \
5
+ pickle_load_from_file, pickle_save_to_file, files_list, files_recursive
6
+ from osbot_utils.utils.Misc import str_md5
7
+ from osbot_utils.utils.Python_Logger import logger_info
8
+
9
+
10
+
11
+ class Cache_Pickle:
12
+
13
+ _cache__FOLDER_CACHE_ROOT_FOLDER = '_cache_pickle'
14
+ _cache__SUPPORTED_PARAMS_TYPES = [int, float, bytearray, bytes, bool, complex, str]
15
+
16
+ def __init__(self):
17
+ self._cache_enabled = True
18
+ #self.log_info = logger_info()
19
+ self._cache_setup() # make sure the cache folder exists
20
+
21
+ def __enter__(self): return self
22
+ def __exit__ (self, type, value, traceback): pass
23
+
24
+ def __getattribute__(self, name):
25
+ if name.startswith('_cache_') or name.startswith('__'): # if the method is a method from Cache_Pickleor a private method
26
+ return super().__getattribute__(name) # just return it's value
27
+ target = super().__getattribute__(name) # get the target
28
+ if not callable(target): # if it is not a function
29
+ return target # just return it
30
+ return self._cache_data(target) # if it is a function, create a wrapper around it
31
+
32
+ def _cache_clear(self):
33
+ cache_dir = self._cache_path()
34
+ for filename in os.listdir(cache_dir):
35
+ if filename.endswith('.pickle'):
36
+ os.remove(os.path.join(cache_dir, filename))
37
+ return self
38
+
39
+ def _cache_data(self, func):
40
+
41
+ @wraps(func)
42
+ def wrapper(*args, **kwargs):
43
+ if type(func.__self__) != type(self): # if the parent type of the function is not self, then just execute it (this happens if a function is set as a variable)
44
+ return func(*args, **kwargs)
45
+
46
+ # first check the params that are specific for this cache method (and cannot be propagated)
47
+ if 'reload_cache' in kwargs: # if the reload parameter is set to True
48
+ reload_cache = kwargs['reload_cache'] # set reload to the value provided
49
+ del kwargs['reload_cache'] # remove the reload parameter from the kwargs
50
+ else:
51
+ reload_cache = False # otherwise set reload to False
52
+
53
+ if 'use_cache' in kwargs: # see if we want to disable cache
54
+ use_cache = kwargs['use_cache']
55
+ del kwargs['use_cache']
56
+ else:
57
+ use_cache = True
58
+
59
+ # after processing these extra params we can resolve the file name and check if it exists
60
+ cache_file_name = self._cache_resolve_file_name(func, args, kwargs)
61
+
62
+ # path_file = path_combine(self._cache_path(), f'{caller_name}.pickle')
63
+ path_file = path_combine(self._cache_path(), cache_file_name)
64
+
65
+ if use_cache is True and reload_cache is False and file_exists(path_file):
66
+ return pickle_load_from_file(path_file)
67
+ else:
68
+ data = func(*args, **kwargs)
69
+ if data and use_cache is True:
70
+ caller_name = func.__name__
71
+ #print(f"Saving cache file data for: {caller_name}")
72
+ pickle_save_to_file(data, path_file)
73
+ return data
74
+ return wrapper
75
+
76
+ def _cache_disable(self):
77
+ self._cache_enabled = False
78
+ return self
79
+
80
+ def _cache_path(self):
81
+ class_name = self.__class__.__name__
82
+ module_name = self.__class__.__module__
83
+ folder_name = f'{self._cache__FOLDER_CACHE_ROOT_FOLDER}/{module_name.replace(".", "/")}'
84
+ if not module_name.endswith(class_name):
85
+ folder_name += f'/{class_name}'
86
+ return path_combine(temp_folder_current(), folder_name)
87
+
88
+ def _cache_files(self):
89
+ return files_recursive(self._cache_path())
90
+
91
+ def _cache_setup(self):
92
+ folder_create(self._cache_path())
93
+ return self
94
+
95
+ def _cache_kwargs_to_str(self, kwargs):
96
+ kwargs_values_as_str = ''
97
+ if kwargs:
98
+ if type(kwargs) is not dict:
99
+ return str(kwargs)
100
+ for key, value in kwargs.items():
101
+ if value and type(value) not in self._cache__SUPPORTED_PARAMS_TYPES:
102
+ value = '(...)'
103
+ kwargs_values_as_str += f'{key}:{value}|'
104
+ return kwargs_values_as_str
105
+
106
+ def _cache_args_to_str(self, args):
107
+ args_values_as_str = ''
108
+ if args:
109
+ if type(args) is not list:
110
+ return str(args)
111
+ for arg in args:
112
+ if not arg or type(arg) in self._cache__SUPPORTED_PARAMS_TYPES:
113
+ arg_value = str(arg)
114
+ else:
115
+ arg_value = '(...)'
116
+ args_values_as_str += f'{arg_value}|'
117
+ return args_values_as_str
118
+
119
+ def _cache_resolve_file_name(self, function, args=None, kwargs=None):
120
+ key_name = function.__name__
121
+ args_md5 = ''
122
+ kwargs_md5 = ''
123
+ args_values_as_str = self._cache_args_to_str(args)
124
+ kwargs_values_as_str = self._cache_kwargs_to_str(kwargs)
125
+ if args_values_as_str : args_md5 = '_' + str_md5(args_values_as_str )[:10]
126
+ if kwargs_values_as_str: kwargs_md5 = '_' + str_md5(kwargs_values_as_str)[:10]
127
+ cache_file_name = f'{key_name}{args_md5}{kwargs_md5}'
128
+ cache_file_name += '.pickle'
129
+ return cache_file_name
@@ -0,0 +1,27 @@
1
+ from osbot_utils.helpers.Local_Cache import Local_Cache
2
+
3
+ class Kwargs_To_Disk:
4
+
5
+ def __enter__(self): return self
6
+ def __exit__ (self, exc_type, exc_val, exc_tb): pass
7
+
8
+ def __init__(self):
9
+ self._cache_name = f'{self.__class__.__module__}___{self.__class__.__name__}'
10
+ self._local_cache = Local_Cache(cache_name=self._cache_name).setup()
11
+
12
+ def __getattr__(self, key):
13
+ if key.startswith('_'):
14
+ return super().__getattribute__(key)
15
+ return self._local_cache.get(key)
16
+
17
+ def __setattr__(self, key, value):
18
+ if key.startswith('_'):
19
+ super().__setattr__(key, value)
20
+ else:
21
+ self._local_cache.set(key, value)
22
+
23
+ def _cache_create (self): return self._local_cache.create ()
24
+ def _cache_delete (self): return self._local_cache.cache_delete ()
25
+ def _cache_data (self): return self._local_cache.data ()
26
+ def _cache_exists (self): return self._local_cache.cache_exists ()
27
+ def _cache_path_data_file(self): return self._local_cache.path_cache_file()
@@ -0,0 +1,308 @@
1
+ # todo: find a way to add these documentations strings to a separate location so that
2
+ # the code is not polluted with them (like in the example below)
3
+ # the data is available in IDE's code complete
4
+ import functools
5
+ import inspect
6
+ import types
7
+ from decimal import Decimal
8
+ from enum import Enum, EnumMeta, EnumType
9
+ from typing import get_origin, get_args
10
+
11
+ from osbot_utils.base_classes.Type_Safe__List import Type_Safe__List
12
+ from osbot_utils.utils.Dev import pprint
13
+ from osbot_utils.utils.Json import json_parse
14
+ from osbot_utils.utils.Misc import list_set
15
+ from osbot_utils.utils.Objects import default_value, value_type_matches_obj_annotation_for_attr, \
16
+ raise_exception_on_obj_type_annotation_mismatch, obj_is_attribute_annotation_of_type, enum_from_value, \
17
+ obj_is_type_union_compatible, value_type_matches_obj_annotation_for_union_attr
18
+
19
+ immutable_types = (bool, int, float, complex, str, tuple, frozenset, bytes, types.NoneType, EnumMeta)
20
+
21
+
22
+ #todo: see if we can also add type safety to method execution
23
+ # for example if we have an method like def add_node(self, title: str, call_index: int):
24
+ # throw an exception if the type of the value passed in is not the same as the one defined in the method
25
+
26
+ class Kwargs_To_Self: # todo: check if the description below is still relevant (since a lot has changed since it was created)
27
+ """
28
+ A mixin class to strictly assign keyword arguments to pre-defined instance attributes during initialization.
29
+
30
+ This base class provides an __init__ method that assigns values from keyword
31
+ arguments to instance attributes. If an attribute with the same name as a key
32
+ from the kwargs is defined in the class, it will be set to the value from kwargs.
33
+ If the key does not match any predefined attribute names, an exception is raised.
34
+
35
+ This behavior enforces strict control over the attributes of instances, ensuring
36
+ that only predefined attributes can be set at the time of instantiation and avoids
37
+ silent attribute creation which can lead to bugs in the code.
38
+
39
+ Usage:
40
+ class MyConfigurableClass(Kwargs_To_Self):
41
+ attribute1 = 'default_value'
42
+ attribute2 = True
43
+ attribute3 : str
44
+ attribute4 : list
45
+ attribute4 : int = 42
46
+
47
+ # Other methods can be added here
48
+
49
+ # Correctly override default values by passing keyword arguments
50
+ instance = MyConfigurableClass(attribute1='new_value', attribute2=False)
51
+
52
+ # This will raise an exception as 'attribute3' is not predefined
53
+ # instance = MyConfigurableClass(attribute3='invalid_attribute')
54
+
55
+ this will also assign the default value to any variable that has a type defined.
56
+ In the example above the default values (mapped by __default__kwargs__ and __locals__) will be:
57
+ attribute1 = 'default_value'
58
+ attribute2 = True
59
+ attribute3 = '' # default value of str
60
+ attribute4 = [] # default value of list
61
+ attribute4 = 42 # defined value in the class
62
+
63
+ Note:
64
+ It is important that all attributes which may be set at instantiation are
65
+ predefined in the class. Failure to do so will result in an exception being
66
+ raised.
67
+
68
+ Methods:
69
+ __init__(**kwargs): The initializer that handles the assignment of keyword
70
+ arguments to instance attributes. It enforces strict
71
+ attribute assignment rules, only allowing attributes
72
+ that are already defined in the class to be set.
73
+ """
74
+
75
+ #__lock_attributes__ = False
76
+ #__type_safety__ = True
77
+
78
+ def __init__(self, **kwargs):
79
+ """
80
+ Initialize an instance of the derived class, strictly assigning provided keyword
81
+ arguments to corresponding instance attributes.
82
+
83
+ Parameters:
84
+ **kwargs: Variable length keyword arguments.
85
+
86
+ Raises:
87
+ Exception: If a key from kwargs does not correspond to any attribute
88
+ pre-defined in the class, an exception is raised to prevent
89
+ setting an undefined attribute.
90
+
91
+ """
92
+ # if 'disable_type_safety' in kwargs: # special case
93
+ # self.__type_safety__ = kwargs['disable_type_safety'] is False
94
+ # del kwargs['disable_type_safety']
95
+
96
+ for (key, value) in self.__cls_kwargs__().items(): # assign all default values to self
97
+ if value is not None: # when the value is explicitly set to None on the class static vars, we can't check for type safety
98
+ raise_exception_on_obj_type_annotation_mismatch(self, key, value)
99
+ if hasattr(self, key):
100
+ existing_value = getattr(self, key)
101
+ if existing_value is not None:
102
+ setattr(self, key, existing_value)
103
+ continue
104
+ setattr(self, key, value)
105
+
106
+ for (key, value) in kwargs.items(): # overwrite with values provided in ctor
107
+ if hasattr(self, key):
108
+ if value is not None: # prevent None values from overwriting existing values, which is quite common in default constructors
109
+ setattr(self, key, value)
110
+ else:
111
+ raise Exception(f"{self.__class__.__name__} has no attribute '{key}' and cannot be assigned the value '{value}'. "
112
+ f"Use {self.__class__.__name__}.__default_kwargs__() see what attributes are available")
113
+
114
+ def __enter__(self): return self
115
+ def __exit__(self, exc_type, exc_val, exc_tb): pass
116
+
117
+ def __setattr__(self, name, value):
118
+ # if self.__type_safety__:
119
+ # if self.__lock_attributes__:
120
+ # todo: this can't work on all, current hypothesis is that this will work for the values that are explicitly set
121
+ # if not hasattr(self, name):
122
+ # raise AttributeError(f"'[Object Locked] Current object is locked (with __lock_attributes__=True) which prevents new attributes allocations (i.e. setattr calls). In this case {type(self).__name__}' object has no attribute '{name}'") from None
123
+
124
+ if value is not None:
125
+ check_1 = value_type_matches_obj_annotation_for_attr(self, name, value)
126
+ check_2 = value_type_matches_obj_annotation_for_union_attr(self, name, value)
127
+ if (check_1 is False and check_2 is None or
128
+ check_1 is None and check_2 is False or
129
+ check_1 is False and check_2 is False ): # fix for type safety assigment on Union vars
130
+ raise Exception(f"Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'")
131
+ else:
132
+ if hasattr(self, name) and self.__annotations__.get(name) : # don't allow previously set variables to be set to None
133
+ if getattr(self, name) is not None: # unless it is already set to None
134
+ raise Exception(f"Can't set None, to a variable that is already set. Invalid type for attribute '{name}'. Expected '{self.__annotations__.get(name)}' but got '{type(value)}'")
135
+
136
+ super().__setattr__(name, value)
137
+
138
+ def __attr_names__(self):
139
+ return list_set(self.__locals__())
140
+
141
+ @classmethod
142
+ def __cls_kwargs__(cls, include_base_classes=True):
143
+ """Return current class dictionary of class level variables and their values."""
144
+ kwargs = {}
145
+
146
+ for base_cls in inspect.getmro(cls):
147
+ if base_cls is object: # Skip the base 'object' class
148
+ continue
149
+ for k, v in vars(base_cls).items():
150
+ # todo: refactor this logic since it is weird to start with a if not..., and then if ... continue (all these should be if ... continue )
151
+ if not k.startswith('__') and not isinstance(v, types.FunctionType): # remove instance functions
152
+ if isinstance(v, classmethod): # also remove class methods
153
+ continue
154
+ if type(v) is functools._lru_cache_wrapper: # todo, find better way to handle edge cases like this one (which happens when the @cache decorator is used in a instance method that uses Kwargs_To_Self)
155
+ continue
156
+ if (k in kwargs) is False: # do not set the value is it has already been set
157
+ kwargs[k] = v
158
+
159
+ for var_name, var_type in base_cls.__annotations__.items():
160
+ if hasattr(base_cls, var_name) is False: # only add if it has not already been defined
161
+ if var_name in kwargs:
162
+ continue
163
+ var_value = cls.__default__value__(var_type)
164
+ kwargs[var_name] = var_value
165
+ else:
166
+ var_value = getattr(base_cls, var_name)
167
+ if var_value is not None: # allow None assignments on ctor since that is a valid use case
168
+ if var_type and not isinstance(var_value, var_type): # check type
169
+ exception_message = f"variable '{var_name}' is defined as type '{var_type}' but has value '{var_value}' of type '{type(var_value)}'"
170
+ raise Exception(exception_message)
171
+ if var_type not in immutable_types and var_name.startswith('__') is False: # if var_type is not one of the immutable_types or is an __ internal
172
+ #todo: fix type safety bug that I believe is caused here
173
+ if obj_is_type_union_compatible(var_type, immutable_types) is False: # if var_type is not something like Optional[Union[int, str]]
174
+ if type(var_type) not in immutable_types:
175
+ exception_message = f"variable '{var_name}' is defined as type '{var_type}' which is not supported by Kwargs_To_Self, with only the following immutable types being supported: '{immutable_types}'"
176
+ raise Exception(exception_message)
177
+ if include_base_classes is False:
178
+ break
179
+ return kwargs
180
+
181
+ @classmethod
182
+ def __default__value__(cls, var_type):
183
+ if get_origin(var_type) is list: # if we have list defined as list[type]
184
+ item_type = get_args(var_type)[0] # get the type that was defined
185
+ return Type_Safe__List(expected_type=item_type) # and used it as expected_type in Type_Safe__List
186
+ else:
187
+ return default_value(var_type) # for all other cases call default_value, which will try to create a default instance
188
+
189
+ def __default_kwargs__(self):
190
+ """Return entire (including base classes) dictionary of class level variables and their values."""
191
+ kwargs = {}
192
+ cls = type(self)
193
+ for base_cls in inspect.getmro(cls): # Traverse the inheritance hierarchy and collect class-level attributes
194
+ if base_cls is object: # Skip the base 'object' class
195
+ continue
196
+ for k, v in vars(base_cls).items():
197
+ if not k.startswith('__') and not isinstance(v, types.FunctionType): # remove instance functions
198
+ if not isinstance(v, classmethod):
199
+ kwargs[k] = v
200
+ # add the vars defined with the annotations
201
+ for var_name, var_type in base_cls.__annotations__.items():
202
+ var_value = getattr(self, var_name)
203
+ kwargs[var_name] = var_value
204
+
205
+ return kwargs
206
+
207
+ def __kwargs__(self):
208
+ """Return a dictionary of the current instance's attribute values including inherited class defaults."""
209
+ kwargs = {}
210
+ # Update with instance-specific values
211
+ for key, value in self.__default_kwargs__().items():
212
+ kwargs[key] = self.__getattribute__(key)
213
+ # if hasattr(self, key):
214
+ # kwargs[key] = self.__getattribute__(key)
215
+ # else:
216
+ # kwargs[key] = value # todo: see if this is stil a valid scenario
217
+ return kwargs
218
+
219
+
220
+ def __locals__(self):
221
+ """Return a dictionary of the current instance's attribute values."""
222
+ kwargs = self.__kwargs__()
223
+
224
+ if not isinstance(vars(self), types.FunctionType):
225
+ for k, v in vars(self).items():
226
+ if not isinstance(v, types.FunctionType) and not isinstance(v,classmethod):
227
+ if k.startswith('__') is False:
228
+ kwargs[k] = v
229
+ return kwargs
230
+
231
+ @classmethod
232
+ def __schema__(cls):
233
+ return cls.__annotations__
234
+
235
+ # global methods added to any class that base classes this
236
+ # todo: see if there should be a prefix on these methods, to make it easier to spot them
237
+ # of if these are actually that useful that they should be added like this
238
+ def json(self):
239
+ return self.serialize_to_dict()
240
+
241
+ def merge_with(self, target):
242
+ original_attrs = {k: v for k, v in self.__dict__.items() if k not in target.__dict__} # Store the original attributes of self that should be retained.
243
+ self.__dict__ = target.__dict__ # Set the target's __dict__ to self, now self and target share the same __dict__.
244
+ self.__dict__.update(original_attrs) # Reassign the original attributes back to self.
245
+ return self
246
+
247
+ # def locked(self, value=True): # todo: figure out best way to do this (maybe???)
248
+ # self.__lock_attributes__ = value # : update, with the latest changes were we don't show internals on __locals__() this might be a good way to do this
249
+ # return self
250
+
251
+ def reset(self):
252
+ for k,v in self.__cls_kwargs__().items():
253
+ setattr(self, k, v)
254
+
255
+ def update_from_kwargs(self, **kwargs):
256
+ """Update instance attributes with values from provided keyword arguments."""
257
+ for key, value in kwargs.items():
258
+ if value is not None:
259
+ if value_type_matches_obj_annotation_for_attr(self, key, value) is False:
260
+ raise Exception(f"Invalid type for attribute '{key}'. Expected '{self.__annotations__.get(key)}' but got '{type(value)}'")
261
+ setattr(self, key, value)
262
+ return self
263
+
264
+
265
+ def deserialize_from_dict(self, data):
266
+ for key, value in data.items():
267
+ if hasattr(self, key) and isinstance(getattr(self, key), Kwargs_To_Self):
268
+ getattr(self, key).deserialize_from_dict(value) # Recursive call for complex nested objects
269
+ else:
270
+ if obj_is_attribute_annotation_of_type(self, key, EnumType): # handle the case when the value is an Enum
271
+ enum_type = getattr(self, '__annotations__').get(key)
272
+ if type(value) is not enum_type: # if the value is not already of the target type
273
+ value = enum_from_value(enum_type, value) # try to resolve the value into the enum
274
+
275
+ setattr(self, key, value) # Direct assignment for primitive types and other structures
276
+ return self
277
+
278
+ def serialize_to_dict(self): # todo: see if we need this method or if the .json() is enough
279
+ return serialize_to_dict(self)
280
+
281
+ def print(self):
282
+ pprint(serialize_to_dict(self))
283
+
284
+ @classmethod
285
+ def from_json(cls, json_data):
286
+ if type(json_data) is str:
287
+ json_data = json_parse(json_data)
288
+ if json_data: # if there is no data or is {} then don't create an object (since this could be caused by bad data being provided)
289
+ return cls().deserialize_from_dict(json_data)
290
+ return None
291
+
292
+ # todo: see if it is possible to add recursive protection to this logic
293
+ def serialize_to_dict(obj):
294
+ if isinstance(obj, (str, int, float, bool, bytes, Decimal)) or obj is None:
295
+ return obj
296
+ elif isinstance(obj, Enum):
297
+ return obj.name
298
+ elif isinstance(obj, list):
299
+ return [serialize_to_dict(item) for item in obj]
300
+ elif isinstance(obj, dict):
301
+ return {key: serialize_to_dict(value) for key, value in obj.items()}
302
+ elif hasattr(obj, "__dict__"):
303
+ data = {} # todo: look at a more advanced version which saved the type of the object, for example with {'__type__': type(obj).__name__}
304
+ for key, value in obj.__dict__.items():
305
+ data[key] = serialize_to_dict(value) # Recursive call for complex types
306
+ return data
307
+ else:
308
+ raise TypeError(f"Type {type(obj)} not serializable")
@@ -0,0 +1,14 @@
1
+ class Type_Safe__List(list):
2
+
3
+ def __init__(self, expected_type, *args):
4
+ super().__init__(*args)
5
+ self.expected_type = expected_type
6
+
7
+ def __repr__(self):
8
+ return f"list[{self.expected_type.__name__}] with {len(self)} elements"
9
+
10
+ def append(self, item):
11
+ if not isinstance(item, self.expected_type):
12
+ raise TypeError(f"In Type_Safe__List: Invalid type for item: Expected '{self.expected_type.__name__}', but got '{type(item).__name__}'")
13
+ super().append(item)
14
+
File without changes
File without changes
@@ -0,0 +1,33 @@
1
+ from osbot_utils.utils.Misc import timestamp_utc_now
2
+
3
+
4
+ class capture_duration():
5
+ def __init__(self):
6
+ self.duration = None
7
+ self.start_timestamp = None
8
+ self.end_timestamp = None
9
+ self.seconds = None
10
+
11
+ def __enter__(self):
12
+ self.start_timestamp = timestamp_utc_now()
13
+ return self
14
+
15
+ def __exit__(self, exc_type, exc_val, exc_tb):
16
+ self.end_timestamp = timestamp_utc_now()
17
+ self.duration = self.end_timestamp - self.start_timestamp
18
+ self.seconds = round(self.duration / 1000, 3) # Duration in seconds (rounded to the 3 digits)
19
+ return False # ensures that any exceptions that happened are rethrown
20
+
21
+ def data(self):
22
+ return dict(start = self.start_timestamp, end = self.end_timestamp, seconds = self.seconds)
23
+
24
+ def print(self):
25
+ print()
26
+ print(f'action took: {self.seconds} seconds')
27
+
28
+ class print_duration(capture_duration):
29
+
30
+ def __exit__(self, exc_type, exc_val, exc_tb):
31
+ result = super().__exit__(exc_type, exc_val, exc_tb)
32
+ self.print()
33
+ return result
File without changes
File without changes
@@ -0,0 +1,9 @@
1
+
2
+ def singleton(cls):
3
+ assert type(cls) == type # make sure the singleton decorator was added to class/type (vs a function)
4
+ instances = {}
5
+ def get_instance(*args, **kwargs):
6
+ if cls not in instances:
7
+ instances[cls] = cls(*args, **kwargs)
8
+ return instances[cls]
9
+ return get_instance
File without changes
@@ -0,0 +1,12 @@
1
+ from functools import wraps
2
+
3
+ def filter_list(function):
4
+ @wraps(function)
5
+ def wrapper(*args, **kwargs):
6
+ only_show = kwargs.pop('only_show', None) # Directly extract and remove 'only_show' from kwargs if present
7
+ values = function(*args, **kwargs) # Call the decorated function
8
+ if only_show: # Filter the list of dictionaries to only include specified keys
9
+ return [{key: item[key] for key in only_show if key in item} for item in values]
10
+ return values
11
+
12
+ return wrapper
@@ -0,0 +1,21 @@
1
+ #todo: refactor with index_by
2
+ from functools import wraps
3
+
4
+
5
+ def group_by(function): # returns the list provided grouped by the key provided in group_by
6
+ @wraps(function)
7
+ def wrapper(*args,**kwargs):
8
+ key = None
9
+ if 'group_by' in kwargs:
10
+ key = kwargs.get('group_by')
11
+ del kwargs['group_by']
12
+ values = function(*args,**kwargs)
13
+ if key:
14
+ results = {}
15
+ for item in values:
16
+ value = item.get(key)
17
+ if results.get(value) is None: results[value] = []
18
+ results[value].append(item)
19
+ return results
20
+ return values
21
+ return wrapper
@@ -0,0 +1,27 @@
1
+ # todo: find way to also allow the used of function().index_by(key) to working
2
+ # : maybe using Fluent_List
3
+ from functools import wraps
4
+
5
+ from osbot_utils.fluent.Fluent_Dict import Fluent_Dict
6
+
7
+
8
+ def index_by(function): # returns the list provided indexed by the key provided in index_by
9
+ def apply(key, values):
10
+ if key:
11
+ results = {}
12
+ for item in values:
13
+ if type(item) is dict:
14
+ results[item.get(key)] = item
15
+ return Fluent_Dict(results)
16
+ return values
17
+
18
+ @wraps(function)
19
+ def wrapper(*args,**kwargs):
20
+ key = None
21
+ if 'index_by' in kwargs:
22
+ key = kwargs.get('index_by')
23
+ del kwargs['index_by']
24
+ values = function(*args,**kwargs)
25
+
26
+ return apply(key,values)
27
+ return wrapper
File without changes
@@ -0,0 +1,19 @@
1
+ from functools import wraps
2
+ from typing import Any, Callable, TypeVar
3
+
4
+ T = TypeVar('T', bound=Callable[..., Any])
5
+
6
+ def cache(function: T) -> T:
7
+ """
8
+ Use this decorator when wanting to cache a value for all executions of the current process and want to preserve the type completion
9
+ which the one from "from functools import cache" doesn't
10
+ note: that this will cache only one value per function (regardless of the values of *args,**kwargs).
11
+ if you have multiple params that should be cached separately, use the @cache_on_self decorator (or the native @cache from functools)
12
+ """
13
+ @wraps(function)
14
+ def wrapper(*args,**kwargs):
15
+ cache_id= f'osbot_cache_return_value__{function.__name__}'
16
+ if hasattr(function, cache_id) is False: # check if return_value has been set
17
+ setattr(function, cache_id, function(*args,**kwargs)) # invoke function and capture the return value
18
+ return getattr(function, cache_id) # return the return value
19
+ return wrapper