osbot-utils 1.7.7__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- osbot_utils/__init__.py +1 -0
- osbot_utils/base_classes/Cache_Pickle.py +129 -0
- osbot_utils/base_classes/Kwargs_To_Disk.py +27 -0
- osbot_utils/base_classes/Kwargs_To_Self.py +308 -0
- osbot_utils/base_classes/Type_Safe__List.py +14 -0
- osbot_utils/base_classes/__init__.py +0 -0
- osbot_utils/context_managers/__init__.py +0 -0
- osbot_utils/context_managers/capture_duration.py +33 -0
- osbot_utils/decorators/__init__.py +0 -0
- osbot_utils/decorators/classes/__init__.py +0 -0
- osbot_utils/decorators/classes/singleton.py +9 -0
- osbot_utils/decorators/lists/__init__.py +0 -0
- osbot_utils/decorators/lists/filter_list.py +12 -0
- osbot_utils/decorators/lists/group_by.py +21 -0
- osbot_utils/decorators/lists/index_by.py +27 -0
- osbot_utils/decorators/methods/__init__.py +0 -0
- osbot_utils/decorators/methods/cache.py +19 -0
- osbot_utils/decorators/methods/cache_on_function.py +56 -0
- osbot_utils/decorators/methods/cache_on_self.py +78 -0
- osbot_utils/decorators/methods/cache_on_tmp.py +71 -0
- osbot_utils/decorators/methods/capture_exception.py +37 -0
- osbot_utils/decorators/methods/capture_status.py +20 -0
- osbot_utils/decorators/methods/catch.py +13 -0
- osbot_utils/decorators/methods/context.py +11 -0
- osbot_utils/decorators/methods/depreciated.py +79 -0
- osbot_utils/decorators/methods/function_type_check.py +62 -0
- osbot_utils/decorators/methods/obj_as_context.py +6 -0
- osbot_utils/decorators/methods/remove_return_value.py +22 -0
- osbot_utils/decorators/methods/required_fields.py +19 -0
- osbot_utils/fluent/Fluent_Dict.py +19 -0
- osbot_utils/fluent/Fluent_List.py +44 -0
- osbot_utils/fluent/__init__.py +1 -0
- osbot_utils/graphs/__init__.py +0 -0
- osbot_utils/graphs/mermaid/Mermaid.py +75 -0
- osbot_utils/graphs/mermaid/Mermaid__Edge.py +49 -0
- osbot_utils/graphs/mermaid/Mermaid__Graph.py +93 -0
- osbot_utils/graphs/mermaid/Mermaid__Node.py +69 -0
- osbot_utils/graphs/mermaid/Mermaid__Renderer.py +54 -0
- osbot_utils/graphs/mermaid/configs/Mermaid__Edge__Config.py +7 -0
- osbot_utils/graphs/mermaid/configs/Mermaid__Node__Config.py +9 -0
- osbot_utils/graphs/mermaid/configs/Mermaid__Render__Config.py +7 -0
- osbot_utils/graphs/mermaid/examples/Mermaid_Examples__FlowChart.py +98 -0
- osbot_utils/graphs/mermaid/models/Mermaid__Diagram_Direction.py +9 -0
- osbot_utils/graphs/mermaid/models/Mermaid__Diagram__Type.py +17 -0
- osbot_utils/graphs/mermaid/models/Mermaid__Node__Shape.py +30 -0
- osbot_utils/graphs/mgraph/MGraph.py +53 -0
- osbot_utils/graphs/mgraph/MGraph__Config.py +7 -0
- osbot_utils/graphs/mgraph/MGraph__Data.py +139 -0
- osbot_utils/graphs/mgraph/MGraph__Edge.py +27 -0
- osbot_utils/graphs/mgraph/MGraph__Node.py +33 -0
- osbot_utils/graphs/mgraph/MGraph__Random_Graphs.py +27 -0
- osbot_utils/graphs/mgraph/MGraph__Serializer.py +43 -0
- osbot_utils/graphs/mgraph/MGraphs.py +17 -0
- osbot_utils/graphs/mgraph/__init__.py +0 -0
- osbot_utils/helpers/CPrint.py +98 -0
- osbot_utils/helpers/Dict_To_Attr.py +7 -0
- osbot_utils/helpers/Local_Cache.py +111 -0
- osbot_utils/helpers/Local_Caches.py +54 -0
- osbot_utils/helpers/Print_Table.py +369 -0
- osbot_utils/helpers/Python_Audit.py +45 -0
- osbot_utils/helpers/Random_Seed.py +27 -0
- osbot_utils/helpers/SCP.py +58 -0
- osbot_utils/helpers/SSH.py +151 -0
- osbot_utils/helpers/Type_Registry.py +16 -0
- osbot_utils/helpers/__init__.py +0 -0
- osbot_utils/helpers/ast/Ast.py +35 -0
- osbot_utils/helpers/ast/Ast_Base.py +124 -0
- osbot_utils/helpers/ast/Ast_Data.py +28 -0
- osbot_utils/helpers/ast/Ast_Load.py +62 -0
- osbot_utils/helpers/ast/Ast_Merge.py +26 -0
- osbot_utils/helpers/ast/Ast_Node.py +117 -0
- osbot_utils/helpers/ast/Ast_Visit.py +85 -0
- osbot_utils/helpers/ast/Call_Tree.py +38 -0
- osbot_utils/helpers/ast/__init__.py +145 -0
- osbot_utils/helpers/ast/nodes/Ast_Add.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Alias.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_And.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Argument.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Arguments.py +10 -0
- osbot_utils/helpers/ast/nodes/Ast_Assert.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Assign.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Attribute.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Aug_Assign.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Bin_Op.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Bool_Op.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Break.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Call.py +17 -0
- osbot_utils/helpers/ast/nodes/Ast_Class_Def.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Compare.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Comprehension.py +10 -0
- osbot_utils/helpers/ast/nodes/Ast_Constant.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Continue.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Dict.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Eq.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Except_Handler.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Expr.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_For.py +10 -0
- osbot_utils/helpers/ast/nodes/Ast_Function_Def.py +17 -0
- osbot_utils/helpers/ast/nodes/Ast_Generator_Exp.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Gt.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_GtE.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_If.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_If_Exp.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Import.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Import_From.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_In.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Is.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Is_Not.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Keyword.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Lambda.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_List.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_List_Comp.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Load.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Lt.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_LtE.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Mod.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Module.py +20 -0
- osbot_utils/helpers/ast/nodes/Ast_Mult.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Name.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Not.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Not_Eq.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Not_In.py +6 -0
- osbot_utils/helpers/ast/nodes/Ast_Or.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Pass.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Pow.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Raise.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Return.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Set.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Slice.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Starred.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Store.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Sub.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Subscript.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_Try.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Tuple.py +9 -0
- osbot_utils/helpers/ast/nodes/Ast_Unary_Op.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_While.py +8 -0
- osbot_utils/helpers/ast/nodes/Ast_With.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_With_Item.py +7 -0
- osbot_utils/helpers/ast/nodes/Ast_Yield.py +7 -0
- osbot_utils/helpers/ast/nodes/__init__.py +0 -0
- osbot_utils/helpers/html/Dict_To_Css.py +20 -0
- osbot_utils/helpers/html/Dict_To_Html.py +59 -0
- osbot_utils/helpers/html/Dict_To_Tags.py +88 -0
- osbot_utils/helpers/html/Html_To_Dict.py +75 -0
- osbot_utils/helpers/html/Html_To_Tag.py +20 -0
- osbot_utils/helpers/html/Tag__Base.py +91 -0
- osbot_utils/helpers/html/Tag__Body.py +5 -0
- osbot_utils/helpers/html/Tag__Div.py +5 -0
- osbot_utils/helpers/html/Tag__H.py +9 -0
- osbot_utils/helpers/html/Tag__HR.py +5 -0
- osbot_utils/helpers/html/Tag__Head.py +32 -0
- osbot_utils/helpers/html/Tag__Html.py +42 -0
- osbot_utils/helpers/html/Tag__Link.py +17 -0
- osbot_utils/helpers/html/Tag__Style.py +25 -0
- osbot_utils/helpers/html/__init__.py +0 -0
- osbot_utils/helpers/pubsub/Event__Queue.py +95 -0
- osbot_utils/helpers/pubsub/PubSub__Client.py +53 -0
- osbot_utils/helpers/pubsub/PubSub__Room.py +13 -0
- osbot_utils/helpers/pubsub/PubSub__Server.py +94 -0
- osbot_utils/helpers/pubsub/PubSub__Sqlite.py +24 -0
- osbot_utils/helpers/pubsub/__init__.py +0 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event.py +15 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Connect.py +7 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Disconnect.py +7 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Join_Room.py +8 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Leave_Room.py +8 -0
- osbot_utils/helpers/pubsub/schemas/Schema__Event__Message.py +7 -0
- osbot_utils/helpers/pubsub/schemas/Schema__PubSub__Client.py +8 -0
- osbot_utils/helpers/pubsub/schemas/__init__.py +0 -0
- osbot_utils/helpers/sqlite/Capture_Sqlite_Error.py +51 -0
- osbot_utils/helpers/sqlite/Sqlite__Cursor.py +87 -0
- osbot_utils/helpers/sqlite/Sqlite__Database.py +137 -0
- osbot_utils/helpers/sqlite/Sqlite__Field.py +70 -0
- osbot_utils/helpers/sqlite/Sqlite__Globals.py +5 -0
- osbot_utils/helpers/sqlite/Sqlite__Table.py +293 -0
- osbot_utils/helpers/sqlite/Sqlite__Table__Create.py +96 -0
- osbot_utils/helpers/sqlite/Temp_Sqlite__Database__Disk.py +17 -0
- osbot_utils/helpers/sqlite/Temp_Sqlite__Table.py +23 -0
- osbot_utils/helpers/sqlite/__init__.py +0 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests.py +214 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests__Patch.py +63 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Files.py +23 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Graph.py +47 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Json.py +83 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Local.py +20 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Requests.py +39 -0
- osbot_utils/helpers/sqlite/domains/__init__.py +0 -0
- osbot_utils/helpers/sqlite/domains/schemas/Schema__Table__Requests.py +12 -0
- osbot_utils/helpers/sqlite/domains/schemas/__init__.py +0 -0
- osbot_utils/helpers/sqlite/models/Sqlite__Field__Type.py +37 -0
- osbot_utils/helpers/sqlite/models/__init__.py +0 -0
- osbot_utils/helpers/sqlite/sample_data/Sqlite__Sample_Data__Chinook.py +116 -0
- osbot_utils/helpers/sqlite/sample_data/__init__.py +0 -0
- osbot_utils/helpers/sqlite/sql_builder/SQL_Builder.py +159 -0
- osbot_utils/helpers/sqlite/sql_builder/SQL_Builder__Select.py +12 -0
- osbot_utils/helpers/sqlite/sql_builder/__init__.py +0 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Config.py +63 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Edges.py +46 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Files.py +45 -0
- osbot_utils/helpers/sqlite/tables/Sqlite__Table__Nodes.py +52 -0
- osbot_utils/helpers/sqlite/tables/__init__.py +0 -0
- osbot_utils/helpers/trace/Trace_Call.py +120 -0
- osbot_utils/helpers/trace/Trace_Call__Config.py +94 -0
- osbot_utils/helpers/trace/Trace_Call__Graph.py +26 -0
- osbot_utils/helpers/trace/Trace_Call__Handler.py +215 -0
- osbot_utils/helpers/trace/Trace_Call__Print_Lines.py +85 -0
- osbot_utils/helpers/trace/Trace_Call__Print_Traces.py +170 -0
- osbot_utils/helpers/trace/Trace_Call__Stack.py +166 -0
- osbot_utils/helpers/trace/Trace_Call__Stack_Node.py +59 -0
- osbot_utils/helpers/trace/Trace_Call__Stats.py +71 -0
- osbot_utils/helpers/trace/Trace_Call__View_Model.py +75 -0
- osbot_utils/helpers/trace/Trace_Files.py +33 -0
- osbot_utils/helpers/trace/__init__.py +0 -0
- osbot_utils/testing/Catch.py +54 -0
- osbot_utils/testing/Duration.py +69 -0
- osbot_utils/testing/Hook_Method.py +118 -0
- osbot_utils/testing/Log_To_Queue.py +46 -0
- osbot_utils/testing/Log_To_String.py +37 -0
- osbot_utils/testing/Logging.py +81 -0
- osbot_utils/testing/Patch_Print.py +52 -0
- osbot_utils/testing/Profiler.py +89 -0
- osbot_utils/testing/Stderr.py +19 -0
- osbot_utils/testing/Stdout.py +19 -0
- osbot_utils/testing/Temp_File.py +46 -0
- osbot_utils/testing/Temp_Folder.py +114 -0
- osbot_utils/testing/Temp_Sys_Path.py +13 -0
- osbot_utils/testing/Temp_Web_Server.py +83 -0
- osbot_utils/testing/Temp_Zip.py +45 -0
- osbot_utils/testing/Temp_Zip_In_Memory.py +90 -0
- osbot_utils/testing/Unit_Test.py +34 -0
- osbot_utils/testing/Unzip_File.py +30 -0
- osbot_utils/testing/__init__.py +0 -0
- osbot_utils/utils/Assert.py +52 -0
- osbot_utils/utils/Call_Stack.py +187 -0
- osbot_utils/utils/Csv.py +32 -0
- osbot_utils/utils/Dev.py +47 -0
- osbot_utils/utils/Exceptions.py +7 -0
- osbot_utils/utils/Files.py +528 -0
- osbot_utils/utils/Functions.py +113 -0
- osbot_utils/utils/Http.py +136 -0
- osbot_utils/utils/Int.py +6 -0
- osbot_utils/utils/Json.py +171 -0
- osbot_utils/utils/Json_Cache.py +59 -0
- osbot_utils/utils/Lists.py +198 -0
- osbot_utils/utils/Misc.py +496 -0
- osbot_utils/utils/Objects.py +341 -0
- osbot_utils/utils/Png.py +29 -0
- osbot_utils/utils/Process.py +73 -0
- osbot_utils/utils/Python_Logger.py +301 -0
- osbot_utils/utils/Status.py +79 -0
- osbot_utils/utils/Str.py +63 -0
- osbot_utils/utils/Version.py +16 -0
- osbot_utils/utils/Zip.py +97 -0
- osbot_utils/utils/__init__.py +16 -0
- osbot_utils/version +1 -0
- osbot_utils-1.7.7.dist-info/LICENSE +201 -0
- osbot_utils-1.7.7.dist-info/METADATA +46 -0
- osbot_utils-1.7.7.dist-info/RECORD +260 -0
- osbot_utils-1.7.7.dist-info/WHEEL +4 -0
osbot_utils/__init__.py
ADDED
@@ -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
|