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,56 @@
1
+ import inspect
2
+ from functools import wraps
3
+
4
+ from osbot_utils.utils.Misc import str_md5
5
+
6
+ CACHE_ON_SELF_KEY_PREFIX = 'cache_on_function'
7
+ CACHE_ON_SELF_TYPES = [int, float, bytearray, bytes, bool,
8
+ complex, str]
9
+
10
+ # todo: refactor with cache_on_self (since there is quite a lot of shared code)
11
+ def cache_on_function(function):
12
+ """
13
+ Use this for cases where we want the cache to be tied to the function instance or static method
14
+ """
15
+ @wraps(function)
16
+ def wrapper(*args, **kwargs):
17
+ target = function # use function as the cache target
18
+ if 'reload_cache' in kwargs: # if the reload parameter is set to True
19
+ reload_cache = True # set reload to True
20
+ del kwargs['reload_cache'] # remove the reload parameter from the kwargs
21
+ else:
22
+ reload_cache = False # otherwise set reload to False
23
+ cache_id = cache_on_self__get_cache_in_key(function, args, kwargs)
24
+ if reload_cache is True or hasattr(target, cache_id) is False: # check if return_value has been set or if reload is True
25
+ return_value = function(*args, **kwargs) # invoke function and capture the return value
26
+ setattr(target, cache_id,return_value) # set the return value
27
+ return getattr(target, cache_id) # return the return value
28
+ return wrapper
29
+
30
+ def cache_on_self__args_to_str(args):
31
+ args_values_as_str = ''
32
+ if args:
33
+ for arg in args:
34
+ if type(arg) in CACHE_ON_SELF_TYPES:
35
+ args_values_as_str += str(arg)
36
+ return args_values_as_str
37
+
38
+ def cache_on_self__kwargs_to_str(kwargs):
39
+ kwargs_values_as_str = ''
40
+ if kwargs:
41
+ for key,value in kwargs.items():
42
+ if type(value) in CACHE_ON_SELF_TYPES:
43
+ kwargs_values_as_str += f'{key}:{value}|'
44
+ return kwargs_values_as_str
45
+
46
+ def cache_on_self__get_cache_in_key(function, args=None, kwargs=None):
47
+ key_name = function.__name__
48
+ args_md5 = ''
49
+ kwargs_md5 = ''
50
+ args_values_as_str = cache_on_self__args_to_str(args)
51
+ kwargs_values_as_str = cache_on_self__kwargs_to_str(kwargs)
52
+ if args_values_as_str:
53
+ args_md5 = str_md5(args_values_as_str)
54
+ if kwargs_values_as_str:
55
+ kwargs_md5 = str_md5(kwargs_values_as_str)
56
+ return f'{CACHE_ON_SELF_KEY_PREFIX}_{key_name}_{args_md5}_{kwargs_md5}'
@@ -0,0 +1,78 @@
1
+ import inspect
2
+ from functools import wraps
3
+
4
+ from osbot_utils.utils.Misc import str_md5
5
+ from typing import Any, Callable, TypeVar
6
+
7
+
8
+ CACHE_ON_SELF_KEY_PREFIX = '__cache_on_self__'
9
+ CACHE_ON_SELF_TYPES = [int, float, bytearray, bytes, bool,
10
+ complex, str]
11
+
12
+ # not supported for now (need to understand side effect, )
13
+ # - set, dict, range,, tuple, list : cloud have inner objects
14
+ # - memoryview : returns unique memory location value
15
+
16
+
17
+
18
+
19
+ T = TypeVar('T', bound=Callable[..., Any]) # so that we have type hinting when using this class
20
+
21
+ def cache_on_self(function: T) -> T:
22
+ """
23
+ Use this for cases where we want the cache to be tied to the Class instance (i.e. not global for all executions)
24
+ """
25
+ @wraps(function)
26
+ def wrapper(*args, **kwargs):
27
+ if len(args) == 0 or inspect.isclass(type(args[0])) is False:
28
+ raise Exception("In Method_Wrappers.cache_on_self could not find self")
29
+ if 'reload_cache' in kwargs: # if the reload parameter is set to True
30
+ reload_cache = True # set reload to True
31
+ del kwargs['reload_cache'] # remove the reload parameter from the kwargs
32
+ else:
33
+ reload_cache = False # otherwise set reload to False
34
+ self = args[0] # get self
35
+ cache_id = cache_on_self__get_cache_in_key(function, args, kwargs)
36
+ if reload_cache is True or hasattr(self, cache_id) is False: # check if return_value has been set or if reload is True
37
+ return_value = function(*args, **kwargs) # invoke function and capture the return value
38
+ setattr(self, cache_id,return_value) # set the return value
39
+ return getattr(self, cache_id) # return the return value
40
+ return wrapper
41
+
42
+ def cache_on_self__args_to_str(args):
43
+ args_values_as_str = ''
44
+ if args:
45
+ for arg in args:
46
+ if type(arg) in CACHE_ON_SELF_TYPES:
47
+ args_values_as_str += str(arg)
48
+ return args_values_as_str
49
+
50
+ def cache_on_self__kwargs_to_str(kwargs):
51
+ kwargs_values_as_str = ''
52
+ if kwargs:
53
+ for key,value in kwargs.items():
54
+ if type(value) in CACHE_ON_SELF_TYPES:
55
+ kwargs_values_as_str += f'{key}:{value}|'
56
+ return kwargs_values_as_str
57
+
58
+ def cache_on_self__get_cache_in_key(function, args=None, kwargs=None):
59
+ key_name = function.__name__
60
+ args_md5 = ''
61
+ kwargs_md5 = ''
62
+ args_values_as_str = cache_on_self__args_to_str(args)
63
+ kwargs_values_as_str = cache_on_self__kwargs_to_str(kwargs)
64
+ if args_values_as_str:
65
+ args_md5 = str_md5(args_values_as_str)
66
+ if kwargs_values_as_str:
67
+ kwargs_md5 = str_md5(kwargs_values_as_str)
68
+ return f'{CACHE_ON_SELF_KEY_PREFIX}_{key_name}_{args_md5}_{kwargs_md5}'
69
+
70
+ # class_name = self_obj.__class__.__name__
71
+ #
72
+ # function_name = function_obj.__name__
73
+ # if params:
74
+ # params_as_string = '_'.join(str(x) for x in params).replace('/',' ')
75
+ # params_md5 = str_md5(params_as_string)
76
+ # return f'{class_name}_{function_name}_{params_md5}.gz'
77
+ # else:
78
+ # return f'{class_name}_{function_name}.gz'
@@ -0,0 +1,71 @@
1
+ from osbot_utils.utils.Misc import str_md5
2
+
3
+ from osbot_utils.utils.Files import temp_folder_current, path_combine, folder_create
4
+
5
+ from osbot_utils.utils.Json import json_load_file_gz, json_save_file_gz
6
+
7
+ #todo: add feature to only cache for some time (for example 2 minutes or 2 hours)
8
+ # this will solve a number of probs with the current usability of this data
9
+
10
+ class cache_on_tmp:
11
+ """
12
+ Caches the return value of the wrapped method in tmp folder
13
+ Takes into account the request params to create the file name used for caching
14
+ """
15
+ def __init__(self, reload_data=False, return_cache_key=False):
16
+ self.cache_folder_name = "osbot_cache_on_tmp"
17
+ self.cache_folder = path_combine(temp_folder_current(), self.cache_folder_name)
18
+ self.last_cache_path = None
19
+ self.return_cache_key = return_cache_key
20
+ self.reload_data = reload_data
21
+ folder_create(self.cache_folder)
22
+ #print(self.last_cache_path)
23
+
24
+ def __call__(self, function):
25
+ def wrapper(*args,**kwargs):
26
+ params = list(args)
27
+ if len(params) > 0:
28
+ self_obj = params.pop(0)
29
+ else:
30
+ self_obj = self # has the side effect that the cache key is only locked to the method name
31
+ cache_path = self.get_cache_in_tmp_path(self_obj, function, params)
32
+ if self.return_cache_key:
33
+ return cache_path
34
+ data = self.get_cache_in_tmp_data(cache_path)
35
+ if data and self.reload_data is False:
36
+ return data
37
+
38
+ function_data = function(*args,**kwargs)
39
+ return self.save_cache_in_tmp_data(cache_path,function_data)
40
+
41
+ return wrapper
42
+
43
+ def get_cache_in_tmp_key(self, self_obj, function_obj, params):
44
+ class_name = self_obj.__class__.__name__
45
+ # if function_obj.__class__.__name__ == 'str': # todo: check if this is needed
46
+ # function_name = function_obj
47
+ # else:
48
+ # function_name = function_obj.__name__
49
+ function_name = function_obj.__name__
50
+ if params:
51
+ params_as_string = '_'.join(str(x) for x in params).replace('/',' ')
52
+ params_md5 = str_md5(params_as_string)
53
+ return f'{class_name}_{function_name}_{params_md5}.gz'
54
+ else:
55
+ return f'{class_name}_{function_name}.gz'
56
+
57
+ def get_cache_in_tmp_path(self, self_obj, function_obj, params):
58
+ cache_key = self.get_cache_in_tmp_key(self_obj, function_obj, params)
59
+ cache_path = path_combine(self.cache_folder,cache_key)
60
+ self.last_cache_path = cache_path
61
+ return cache_path
62
+ #return '/tmp/cache_in_tmp_{0}.gz'.format(cache_key)
63
+
64
+ # todo: refactor to use pickle for data load
65
+ def get_cache_in_tmp_data(self, cache_path):
66
+ return json_load_file_gz(path=cache_path)
67
+
68
+ # todo: refactor to use pickle for data save
69
+ def save_cache_in_tmp_data(self, cache_path, data):
70
+ json_save_file_gz(path=cache_path, python_object=data)
71
+ return data
@@ -0,0 +1,37 @@
1
+ import traceback
2
+ from functools import wraps
3
+ from osbot_utils.utils.Dev import pprint
4
+
5
+ def capture_exception(func):
6
+ @wraps(func)
7
+ def wrapper(*args, **kwargs):
8
+ return_value = None
9
+ with Capture_Exception() as context:
10
+ return_value = func(*args, **kwargs)
11
+ if context.error_occurred:
12
+ print("\n****** EXCEPTION DETECTED ******")
13
+ pprint(context.error_details)
14
+ return return_value
15
+ return wrapper
16
+
17
+ class Capture_Exception:
18
+ error_occurred : bool
19
+ error_details : dict
20
+
21
+ def __enter__(self):
22
+ self.error_occurred = False
23
+ self.error_details = {}
24
+ return self
25
+
26
+ def __exit__(self, exc_type, exc_val, exc_tb):
27
+ if exc_val:
28
+ self.error_occurred = True
29
+ last_frame = traceback.extract_tb(exc_tb)[-1]
30
+ self.error_details = {
31
+ 'exception_type': str(exc_type.__name__),
32
+ 'message' : str(exc_val),
33
+ 'last_frame' : { 'file': last_frame.filename,
34
+ 'line': last_frame.lineno }
35
+ }
36
+ return True
37
+ return False
@@ -0,0 +1,20 @@
1
+ from functools import wraps
2
+
3
+ def capture_status(method):
4
+ @wraps(method)
5
+ def wrapper(*args, **kwargs):
6
+ try:
7
+ # Attempt to execute the method
8
+ result = method(*args, **kwargs)
9
+ return {'status': 'ok', 'data': result}
10
+ except Exception as error:
11
+ # Handle any exceptions that occur
12
+ return {'status': 'error', 'error': str(error)}
13
+ return wrapper
14
+
15
+
16
+ def apply_capture_status(cls):
17
+ for attr_name, attr_value in cls.__dict__.items():
18
+ if callable(attr_value) and not attr_name.startswith("__"):
19
+ setattr(cls, attr_name, capture_status(attr_value))
20
+ return cls
@@ -0,0 +1,13 @@
1
+ from functools import wraps
2
+
3
+
4
+ def catch(function):
5
+ """Catches any errors and returns an object with the error
6
+ return: { 'status': 'error', 'error': f'{exception}', 'exception': exception }"""
7
+ @wraps(function)
8
+ def wrapper(*args,**kwargs):
9
+ try:
10
+ return function(*args,**kwargs)
11
+ except Exception as exception:
12
+ return {'status': 'error', 'error': f'{exception}', 'exception': exception} # todo return status_error (could have some side effect on existing codebase)
13
+ return wrapper
@@ -0,0 +1,11 @@
1
+ from contextlib import contextmanager
2
+
3
+ @contextmanager
4
+ def context(target, *args, exec_before=None, exec_after=None, **kwargs):
5
+ if exec_before:
6
+ exec_before(*args, **kwargs)
7
+ try:
8
+ yield target
9
+ finally:
10
+ if exec_after:
11
+ exec_after(*args, **kwargs)
@@ -0,0 +1,79 @@
1
+ # based on code from https://stackoverflow.com/a/40301488/262379
2
+ import functools
3
+ import inspect
4
+ import warnings
5
+
6
+ string_types = (type(b''), type(u''))
7
+
8
+
9
+ def deprecated(reason):
10
+ """
11
+ This is a decorator which can be used to mark functions
12
+ as deprecated. It will result in a warning being emitted
13
+ when the function is used.
14
+ """
15
+
16
+ if isinstance(reason, string_types):
17
+
18
+ # The @deprecated is used with a 'reason'.
19
+ #
20
+ # .. code-block:: python
21
+ #
22
+ # @deprecated("please, use another function")
23
+ # def old_function(x, y):
24
+ # pass
25
+
26
+ def decorator(func1):
27
+
28
+ if inspect.isclass(func1):
29
+ fmt1 = "Call to deprecated class {name} ({reason})."
30
+ else:
31
+ fmt1 = "Call to deprecated function {name} ({reason})."
32
+
33
+ @functools.wraps(func1)
34
+ def new_func1(*args, **kwargs):
35
+ warnings.simplefilter('always', DeprecationWarning)
36
+ warnings.warn(
37
+ fmt1.format(name=func1.__name__, reason=reason),
38
+ category=DeprecationWarning,
39
+ stacklevel=2
40
+ )
41
+ warnings.simplefilter('default', DeprecationWarning)
42
+ return func1(*args, **kwargs)
43
+
44
+ return new_func1
45
+
46
+ return decorator
47
+
48
+ elif inspect.isclass(reason) or inspect.isfunction(reason):
49
+
50
+ # The @deprecated is used without any 'reason'.
51
+ #
52
+ # .. code-block:: python
53
+ #
54
+ # @deprecated
55
+ # def old_function(x, y):
56
+ # pass
57
+
58
+ func2 = reason
59
+
60
+ if inspect.isclass(func2):
61
+ fmt2 = "Call to deprecated class {name}."
62
+ else:
63
+ fmt2 = "Call to deprecated function {name}."
64
+
65
+ @functools.wraps(func2)
66
+ def new_func2(*args, **kwargs):
67
+ warnings.simplefilter('always', DeprecationWarning)
68
+ warnings.warn(
69
+ fmt2.format(name=func2.__name__),
70
+ category=DeprecationWarning,
71
+ stacklevel=2
72
+ )
73
+ warnings.simplefilter('default', DeprecationWarning)
74
+ return func2(*args, **kwargs)
75
+
76
+ return new_func2
77
+
78
+ else:
79
+ raise TypeError(repr(type(reason)))
@@ -0,0 +1,62 @@
1
+ from functools import wraps
2
+
3
+ # this is an attempt at creating something simpler than
4
+ # - https://github.com/agronholm/typeguard
5
+ # - https://github.com/Stewori/pytypes
6
+
7
+ def is_instance_method(function, args):
8
+ if args and len(args) >0:
9
+ first_type = type(args[0])
10
+ function_name = function.__name__
11
+ return hasattr(first_type, function_name)
12
+ return False
13
+
14
+ def function_type_check(function):
15
+
16
+ @wraps(function)
17
+ def wrapper(*args,**kwargs):
18
+ _args = list(args)
19
+ annotations = function.__annotations__
20
+ if is_instance_method(function, args): # if this a instance method
21
+ _args.pop(0) # remove self (for now)
22
+ for name, var_type in annotations.items():
23
+ if name=='return': continue
24
+ if _args:
25
+ value = _args.pop(0)
26
+ else:
27
+ value = kwargs.get(name)
28
+ if value is None:
29
+ continue
30
+ #if isinstance(value, var_type) is False: # false positive with int and bool
31
+ if type(value) is not var_type:
32
+ raise Exception(f'in function "{function.__name__}", the provided param "{value}" was an "{type(value)}" and it was expected to be {var_type}')
33
+ return_value = function(*args, **kwargs)
34
+ return_type = annotations.get('return')
35
+ if annotations.get('return'):
36
+ if type(return_value) is not return_type:
37
+ raise Exception(f'in function "{function.__name__}", the provided return value "{return_value}" was an "{type(return_value)}" and it was expected to be {return_type}')
38
+ return return_value
39
+
40
+ return wrapper
41
+
42
+ # this was causing lots of site effects (for example not working for static methods and
43
+ # methods would not show on code complete (it was working ok for instances methods, but the lack of code complete was an issue)
44
+ # def class_type_check(Target_Class): #
45
+ # class Wrapped_Cls(object):
46
+ # def __init__(self,*args,**kwargs):
47
+ # self.target_class = Target_Class(*args,**kwargs) #
48
+ #
49
+ # def __getattribute__(self,s):
50
+ # try:
51
+ # x = super(Wrapped_Cls,self).__getattribute__(s)
52
+ # except AttributeError:
53
+ # pass
54
+ # else:
55
+ # return x
56
+ # x = self.target_class.__getattribute__(s)
57
+ # if type(x) == type(self.__init__): # it is an instance method
58
+ # return function_type_check(x) # this is equivalent of just decorating the method with function_type_check
59
+ # else:
60
+ # return x
61
+ #
62
+ # return Wrapped_Cls
@@ -0,0 +1,6 @@
1
+ from contextlib import contextmanager
2
+
3
+
4
+ @contextmanager
5
+ def obj_as_context(variable):
6
+ yield variable
@@ -0,0 +1,22 @@
1
+ from functools import wraps
2
+
3
+
4
+ class remove_return_value:
5
+ """
6
+ removes the field from the return value of the function (if it exists)
7
+ """
8
+ def __init__(self, field_name):
9
+ self.field_name = field_name # field to remove
10
+
11
+ def __call__(self, function):
12
+ @wraps(function) # makes __name__ work ok
13
+ def wrapper(*args,**kwargs): # wrapper function
14
+ data = function(*args,**kwargs) # calls wrapped function with original params
15
+ if data and hasattr(data,'get'): # if it is set and has .get method
16
+ if data.get(self.field_name) is not None: # check if field_name exists in data
17
+ del data[self.field_name] # if it does, delete it
18
+ return data # return data received
19
+ return wrapper # return wrapper function
20
+
21
+ #todo: check usages and remove legacy method
22
+ remove = remove_return_value
@@ -0,0 +1,19 @@
1
+ from functools import wraps
2
+
3
+ from osbot_utils.utils.Objects import get_missing_fields
4
+
5
+
6
+ class required_fields:
7
+ """checks that required fields are not null in the current object (does not work for for static methods)"""
8
+ def __init__(self, field_names):
9
+ self.field_names = field_names
10
+
11
+ def __call__(self, function):
12
+ @wraps(function)
13
+ def wrapper(*args, **kwargs):
14
+ target_obj = args[0] # self of the caller
15
+ missing_fields = get_missing_fields(target_obj, self.field_names)
16
+ if len(missing_fields) > 0:
17
+ raise Exception(f'missing fields in {function.__name__}: {missing_fields}')
18
+ return function(*args,**kwargs)
19
+ return wrapper
@@ -0,0 +1,19 @@
1
+ from osbot_utils.fluent.Fluent_List import Fluent_List
2
+
3
+ class Fluent_Dict(dict):
4
+ def __new__(cls, *args, **kwargs):
5
+ first_one = args[0]
6
+ return super().__new__(cls, first_one)
7
+
8
+ def __init__(self,*args,**kwargs):
9
+ self.data = args[0]
10
+ super().__init__(*args,**kwargs)
11
+
12
+ def keys(self):
13
+ return Fluent_List(sorted(list(self.data.keys())))
14
+
15
+ def size(self):
16
+ return len(self.data)
17
+
18
+ def type(self):
19
+ return type(self)
@@ -0,0 +1,44 @@
1
+ class Fluent_List(list):
2
+ def __new__(cls, *args, **kwargs):
3
+ return super().__new__(cls, args[0])
4
+
5
+ def __init__(self,*args,**kwargs):
6
+ super().__init__(*args,**kwargs)
7
+
8
+ def contains(self, item):
9
+ return item in self
10
+
11
+ def index(self, index):
12
+ if index < self.size():
13
+ return self[index]
14
+
15
+ def first(self):
16
+ if self.size() > 0:
17
+ return self[0]
18
+
19
+ def last(self):
20
+ if self.size() > 0:
21
+ return self[self.size() -1]
22
+
23
+ def pop(self):
24
+ """
25
+ normal pop() function with the only variation being that empty lists will return None instead of raising an exception
26
+ """
27
+ if self.size() > 0:
28
+ return super().pop()
29
+
30
+ def push(self, value):
31
+ self.append(value)
32
+ return self
33
+
34
+ def size(self):
35
+ return len(self)
36
+
37
+ def sorted(self):
38
+ return sorted(self)
39
+
40
+ def type(self): # todo: find a way to add this kind of methods to all Fluent Classes
41
+ return type(self)
42
+
43
+
44
+ Fluent_List.sort = Fluent_List.sorted
@@ -0,0 +1 @@
1
+
File without changes
@@ -0,0 +1,75 @@
1
+ from osbot_utils.graphs.mermaid.Mermaid__Renderer import Mermaid__Renderer
2
+ from osbot_utils.graphs.mermaid.Mermaid__Edge import Mermaid__Edge
3
+ from osbot_utils.graphs.mermaid.Mermaid__Graph import Mermaid__Graph
4
+ from osbot_utils.graphs.mermaid.models.Mermaid__Diagram_Direction import Diagram__Direction
5
+ from osbot_utils.graphs.mermaid.models.Mermaid__Diagram__Type import Diagram__Type
6
+ from osbot_utils.utils.Python_Logger import Python_Logger
7
+ from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
8
+
9
+ class Mermaid(Kwargs_To_Self):
10
+ graph : Mermaid__Graph
11
+ renderer : Mermaid__Renderer
12
+ logger : Python_Logger
13
+
14
+ # todo add support for storing the data in sqlite so that the search for existing nodes is efficient
15
+ def add_edge(self, from_node_key, to_node_key, label=None,attributes=None):
16
+ nodes_by_id = self.graph.data().nodes__by_key()
17
+ from_node = nodes_by_id.get(from_node_key)
18
+ to_node = nodes_by_id.get(to_node_key)
19
+ if not from_node:
20
+ from_node = self.add_node(key=from_node_key)
21
+ if not to_node:
22
+ to_node = self.add_node(key=to_node_key)
23
+
24
+ # todo: add back the protection/detection that we get from MGraph class of allow_circle_edges and allow_duplicate_edges
25
+ mermaid_edge = Mermaid__Edge(from_node=from_node, to_node=to_node, label=label, attributes=attributes)
26
+ self.graph.edges.append(mermaid_edge)
27
+ return mermaid_edge
28
+
29
+ def add_directive(self, directive):
30
+ self.renderer.config.directives.append(directive)
31
+ return self
32
+
33
+ def add_node(self, **kwargs):
34
+ return self.graph.add_node(**kwargs)
35
+
36
+ def code(self):
37
+ return self.renderer.code(self.nodes(), self.edges())
38
+
39
+ def code_markdown(self):
40
+ #self.code_create()
41
+ self.code()
42
+ rendered_lines = self.renderer.mermaid_code
43
+ markdown = ['#### Mermaid Graph',
44
+ "```mermaid" ,
45
+ *rendered_lines ,
46
+ "```" ]
47
+
48
+ return '\n'.join(markdown)
49
+
50
+ def edges(self):
51
+ return self.graph.edges
52
+
53
+ def print_code(self):
54
+ print(self.code())
55
+
56
+ def nodes(self):
57
+ return self.graph.nodes
58
+
59
+ def set_direction(self, direction):
60
+ if isinstance(direction, Diagram__Direction):
61
+ self.renderer.diagram_direction = direction
62
+ elif isinstance(direction, str) and direction in Diagram__Direction.__members__:
63
+ self.renderer.diagram_direction = Diagram__Direction[direction]
64
+ return self # If the value can't be set (not a valid name), do nothing
65
+
66
+ def set_diagram_type(self, diagram_type):
67
+ if isinstance(diagram_type, Diagram__Type):
68
+ self.renderer.diagram_type = diagram_type
69
+
70
+ def save(self, target_file=None):
71
+ file_path = target_file or '/tmp/mermaid.md'
72
+
73
+ with open(file_path, 'w') as file:
74
+ file.write(self.code_markdown())
75
+ return file_path