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
@@ -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,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
|