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,54 @@
|
|
1
|
+
from pprint import pprint
|
2
|
+
|
3
|
+
|
4
|
+
class Catch:
|
5
|
+
"""
|
6
|
+
Helper class for cases when the native Python exception traces is too noisy
|
7
|
+
"""
|
8
|
+
def __init__(self, log_exception=False, log_headers=True, logger=None, expected_error=None, catch_exception=True, expect_exception=False):
|
9
|
+
self.log_exception = log_exception
|
10
|
+
self.log_headers = log_headers
|
11
|
+
self.logger = logger or print
|
12
|
+
self.exception_type = None
|
13
|
+
self.exception_value = None
|
14
|
+
self.exception_traceback = None
|
15
|
+
self.execution_complete = False
|
16
|
+
self.expected_error = expected_error
|
17
|
+
self.catch_exception = catch_exception
|
18
|
+
self.expect_exception = expect_exception # this is useful for Unit tests
|
19
|
+
|
20
|
+
def __repr__(self):
|
21
|
+
if self.execution_complete:
|
22
|
+
return f'Catch: {self.exception_type} : {self.exception_value}'
|
23
|
+
else:
|
24
|
+
return f'Catch: (not executed yet)'
|
25
|
+
|
26
|
+
def __enter__(self):
|
27
|
+
return self
|
28
|
+
|
29
|
+
def __exit__(self, exception_type, exception_value, exception_traceback):
|
30
|
+
self.exception_type = exception_type
|
31
|
+
self.exception_value = exception_value
|
32
|
+
self.exception_traceback = exception_traceback
|
33
|
+
self.execution_complete = True
|
34
|
+
if self.log_exception:
|
35
|
+
if exception_type is not None:
|
36
|
+
if self.log_headers:
|
37
|
+
self.log()
|
38
|
+
self.log("********* Catch ***********")
|
39
|
+
self.log(exception_type)
|
40
|
+
self.log()
|
41
|
+
self.log(exception_value)
|
42
|
+
if self.expected_error:
|
43
|
+
self.assert_error_is(self.expected_error)
|
44
|
+
if self.expect_exception and exception_type is None:
|
45
|
+
raise Exception(f'Expected exception: {self.expected_error} but no exception was raised')
|
46
|
+
if self.catch_exception:
|
47
|
+
return True # returning true here will prevent the exception to be propagated (which is the objective of this class :) )
|
48
|
+
return False
|
49
|
+
|
50
|
+
def assert_error_is(self, expected_error):
|
51
|
+
assert str(self) == expected_error , str(self)
|
52
|
+
|
53
|
+
def log(self, message=''):
|
54
|
+
self.logger(message)
|
@@ -0,0 +1,69 @@
|
|
1
|
+
import inspect
|
2
|
+
from datetime import timedelta
|
3
|
+
from functools import wraps
|
4
|
+
|
5
|
+
from osbot_utils.utils.Call_Stack import Call_Stack
|
6
|
+
|
7
|
+
from osbot_utils.utils.Misc import date_time_now, time_delta_to_str
|
8
|
+
|
9
|
+
|
10
|
+
def duration(func):
|
11
|
+
if inspect.iscoroutinefunction(func):
|
12
|
+
# It's an async function
|
13
|
+
@wraps(func)
|
14
|
+
async def async_wrapper(*args, **kwargs):
|
15
|
+
with Duration(prefix=f'.{func.__name__} took'):
|
16
|
+
return await func(*args, **kwargs)
|
17
|
+
return async_wrapper
|
18
|
+
else:
|
19
|
+
# It's a regular function
|
20
|
+
@wraps(func)
|
21
|
+
def sync_wrapper(*args, **kwargs):
|
22
|
+
with Duration(prefix=f'.{func.__name__} took'):
|
23
|
+
return func(*args, **kwargs)
|
24
|
+
return sync_wrapper
|
25
|
+
|
26
|
+
class Duration:
|
27
|
+
"""
|
28
|
+
Helper class for to capture time duration
|
29
|
+
"""
|
30
|
+
def __init__(self, prefix="\nDuration:", print_result=True, use_utc=True, print_stack=False):
|
31
|
+
self.use_utc = use_utc
|
32
|
+
self.print_result = print_result
|
33
|
+
self.prefix = prefix
|
34
|
+
self.start_time = None
|
35
|
+
self.end_time = None
|
36
|
+
self.duration = None
|
37
|
+
self.print_stack = print_stack
|
38
|
+
if True or print_stack:
|
39
|
+
self.call_stack = Call_Stack()
|
40
|
+
|
41
|
+
def __enter__(self):
|
42
|
+
if self.print_stack:
|
43
|
+
self.call_stack.capture()
|
44
|
+
self.start()
|
45
|
+
return self
|
46
|
+
|
47
|
+
def __exit__(self, exception_type, exception_value, exception_traceback):
|
48
|
+
self.end()
|
49
|
+
|
50
|
+
def start(self):
|
51
|
+
self.start_time = date_time_now(use_utc=self.use_utc, return_str=False)
|
52
|
+
|
53
|
+
def end(self):
|
54
|
+
self.end_time = date_time_now(use_utc=self.use_utc, return_str=False)
|
55
|
+
self.duration = self.end_time - self.start_time
|
56
|
+
if self.print_result:
|
57
|
+
print(f"{self.prefix} {time_delta_to_str(self.duration)}")
|
58
|
+
if self.print_stack:
|
59
|
+
self.call_stack.print()
|
60
|
+
|
61
|
+
def milliseconds(self):
|
62
|
+
return self.duration.total_seconds() * 1000
|
63
|
+
|
64
|
+
def seconds(self):
|
65
|
+
return self.duration.total_seconds()
|
66
|
+
|
67
|
+
def set_duration(self,seconds:int):
|
68
|
+
self.duration = timedelta(seconds=seconds)
|
69
|
+
return self
|
@@ -0,0 +1,118 @@
|
|
1
|
+
from osbot_utils.helpers.Print_Table import Print_Table
|
2
|
+
from osbot_utils.utils.Call_Stack import Call_Stack
|
3
|
+
|
4
|
+
from osbot_utils.testing.Duration import Duration
|
5
|
+
|
6
|
+
class Hook_Method:
|
7
|
+
|
8
|
+
def __init__(self, target_module, target_method):
|
9
|
+
self.target_module = target_module
|
10
|
+
self.target_method = target_method
|
11
|
+
self.target = getattr(target_module, target_method)
|
12
|
+
self.wrapper_method = None
|
13
|
+
self.calls = []
|
14
|
+
self.on_before_call = []
|
15
|
+
self.on_after_call = []
|
16
|
+
self.mock_call = None
|
17
|
+
|
18
|
+
def __enter__(self):
|
19
|
+
self.wrap()
|
20
|
+
return self
|
21
|
+
|
22
|
+
def __exit__(self, type, value, traceback):
|
23
|
+
self.unwrap()
|
24
|
+
|
25
|
+
def add_on_after_call(self, on_after_call):
|
26
|
+
"""
|
27
|
+
method to be after before the Hooked call
|
28
|
+
|
29
|
+
method signature: def on_after_call(return_value, *args, **kwargs):
|
30
|
+
should return: return_value
|
31
|
+
"""
|
32
|
+
self.on_after_call.append(on_after_call)
|
33
|
+
return self
|
34
|
+
|
35
|
+
def add_on_before_call(self, on_before_call):
|
36
|
+
"""
|
37
|
+
method to be called before the Hooked call
|
38
|
+
|
39
|
+
method signature: def on_after_call(*args, **kwargs):
|
40
|
+
should return: (args, kwargs)
|
41
|
+
"""
|
42
|
+
self.on_before_call.append(on_before_call)
|
43
|
+
return self
|
44
|
+
|
45
|
+
def calls_count(self):
|
46
|
+
return len(self.calls)
|
47
|
+
|
48
|
+
def calls_last_one(self):
|
49
|
+
if len(self.calls) > 0:
|
50
|
+
return self.calls[-1]
|
51
|
+
|
52
|
+
def after_call(self, return_value, *args, **kwargs):
|
53
|
+
"""
|
54
|
+
call all methods added via `add_on_after_call` with the params: return_value, *args, **kwargs
|
55
|
+
return value from each on_after_call will on override existing return_value
|
56
|
+
"""
|
57
|
+
for method in self.on_after_call:
|
58
|
+
return_value = method(return_value, *args, **kwargs)
|
59
|
+
return return_value
|
60
|
+
|
61
|
+
def before_call(self, *args, **kwargs):
|
62
|
+
"""
|
63
|
+
call all methods added via `add_on_before_call` with the params: *args, **kwargs
|
64
|
+
return value is expected to be args and kwargs on each on_after_call which will on override existing args and kwargs values
|
65
|
+
"""
|
66
|
+
for method in self.on_before_call:
|
67
|
+
(args, kwargs) = method(*args, **kwargs)
|
68
|
+
return (args, kwargs)
|
69
|
+
|
70
|
+
def print(self):
|
71
|
+
print()
|
72
|
+
print()
|
73
|
+
with Print_Table() as _:
|
74
|
+
_.print(self.calls)
|
75
|
+
|
76
|
+
def set_mock_call(self, mock_call):
|
77
|
+
"""
|
78
|
+
Use this to simulate a call to the Hooked Method (the
|
79
|
+
"""
|
80
|
+
self.mock_call = mock_call
|
81
|
+
|
82
|
+
def wrap(self):
|
83
|
+
|
84
|
+
def wrapper_method(*args, **kwargs):
|
85
|
+
call_stack= Call_Stack().capture()
|
86
|
+
with Duration(print_result=False) as duration:
|
87
|
+
exception = None
|
88
|
+
if self.mock_call:
|
89
|
+
return_value = self.mock_call(*args,**kwargs)
|
90
|
+
else:
|
91
|
+
(args, kwargs) = self.before_call(*args, **kwargs)
|
92
|
+
try:
|
93
|
+
return_value = self.target(*args, **kwargs)
|
94
|
+
return_value = self.after_call(return_value, args, kwargs)
|
95
|
+
except Exception as error:
|
96
|
+
return_value = None
|
97
|
+
exception = error
|
98
|
+
#raise error
|
99
|
+
|
100
|
+
call = {
|
101
|
+
'args' : args ,
|
102
|
+
'call_stack' : call_stack ,
|
103
|
+
'exception' : exception ,
|
104
|
+
'kwargs' : kwargs ,
|
105
|
+
'return_value': return_value ,
|
106
|
+
'index' : len(self.calls) ,
|
107
|
+
'duration' : int(duration.seconds()*1000)
|
108
|
+
}
|
109
|
+
self.calls.append(call)
|
110
|
+
return call['return_value']
|
111
|
+
|
112
|
+
self.wrapper_method = wrapper_method
|
113
|
+
setattr(self.target_module, self.target_method, self.wrapper_method)
|
114
|
+
return self.wrapper_method
|
115
|
+
|
116
|
+
def unwrap(self):
|
117
|
+
setattr(self.target_module, self.target_method, self.target)
|
118
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# todo: finish code below so that log entries are stored in the queue
|
2
|
+
# expand to other capabilites of QueueHandler (namely using multiple handlers)
|
3
|
+
# import logging
|
4
|
+
# import queue
|
5
|
+
# from io import StringIO
|
6
|
+
# from logging.handlers import QueueListener, QueueHandler
|
7
|
+
#
|
8
|
+
#
|
9
|
+
# class Log_To_Queue():
|
10
|
+
#
|
11
|
+
# def __init__(self,logger):
|
12
|
+
# self.logger = logger
|
13
|
+
# self.queue = None
|
14
|
+
# self.queue_listener = None
|
15
|
+
# self.queue_handler = None
|
16
|
+
#
|
17
|
+
# def __enter__(self):
|
18
|
+
# self.add_handler()
|
19
|
+
# return self
|
20
|
+
#
|
21
|
+
# def __exit__(self, exception_type, exception_value, exception_traceback):
|
22
|
+
# self.remove_handler()
|
23
|
+
#
|
24
|
+
# def add_handler(self):
|
25
|
+
# self.queue = queue.Queue(-1)
|
26
|
+
# self.queue_listener = QueueListener(self.queue) # not using any handlers
|
27
|
+
# self.queue_handler = QueueHandler (self.queue)
|
28
|
+
# self.logger.addHandler(self.queue_handler)
|
29
|
+
# self.queue_listener.start()
|
30
|
+
#
|
31
|
+
# def contents(self):
|
32
|
+
# return "self.string_stream.getvalue()"
|
33
|
+
#
|
34
|
+
# def set_level(self, level):
|
35
|
+
# self.logger.setLevel(level)
|
36
|
+
# return self
|
37
|
+
#
|
38
|
+
# def set_level_critical(self): return self.set_level('CRITICAL') # level 50
|
39
|
+
# def set_level_debug (self): return self.set_level('DEBUG' ) # level 10
|
40
|
+
# def set_level_error (self): return self.set_level('ERROR' ) # level 40
|
41
|
+
# def set_level_info (self): return self.set_level('INFO' ) # level 20
|
42
|
+
# def set_level_warning (self): return self.set_level('WARNING' ) # level 30
|
43
|
+
#
|
44
|
+
# def remove_handler(self):
|
45
|
+
# self.queue_listener.stop()
|
46
|
+
# self.logger.removeHandler(self.queue_handler)
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import logging
|
2
|
+
from io import StringIO
|
3
|
+
|
4
|
+
class Log_To_String():
|
5
|
+
|
6
|
+
def __init__(self,logger):
|
7
|
+
self.logger = logger
|
8
|
+
self.string_stream = None
|
9
|
+
self.string_handler = None
|
10
|
+
|
11
|
+
def __enter__(self):
|
12
|
+
self.add_handler()
|
13
|
+
return self
|
14
|
+
|
15
|
+
def __exit__(self, exception_type, exception_value, exception_traceback):
|
16
|
+
self.remove_handler()
|
17
|
+
|
18
|
+
def add_handler(self):
|
19
|
+
self.string_stream = StringIO()
|
20
|
+
self.string_handler = logging.StreamHandler(self.string_stream)
|
21
|
+
self.logger.addHandler(self.string_handler)
|
22
|
+
|
23
|
+
def contents(self):
|
24
|
+
return self.string_stream.getvalue()
|
25
|
+
|
26
|
+
def set_level(self, level):
|
27
|
+
self.logger.setLevel(level)
|
28
|
+
return self
|
29
|
+
|
30
|
+
def set_level_critical(self): return self.set_level('CRITICAL') # level 50
|
31
|
+
def set_level_debug (self): return self.set_level('DEBUG' ) # level 10
|
32
|
+
def set_level_error (self): return self.set_level('ERROR' ) # level 40
|
33
|
+
def set_level_info (self): return self.set_level('INFO' ) # level 20
|
34
|
+
def set_level_warning (self): return self.set_level('WARNING' ) # level 30
|
35
|
+
|
36
|
+
def remove_handler(self):
|
37
|
+
self.logger.removeHandler(self.string_handler)
|
@@ -0,0 +1,81 @@
|
|
1
|
+
import inspect
|
2
|
+
import logging
|
3
|
+
from io import StringIO
|
4
|
+
|
5
|
+
import sys
|
6
|
+
|
7
|
+
from osbot_utils.decorators.methods.cache_on_self import cache_on_self
|
8
|
+
|
9
|
+
#DEFAULT_LOG_FORMAT = '%(asctime)s.%(msecs)03d %(levelname)s - %(message)s'
|
10
|
+
DEFAULT_LOG_FORMAT = '%(levelname)s - %(message)s'
|
11
|
+
DEFAULT_LOG_LEVEL = logging.DEBUG
|
12
|
+
DEFAULT_DATE_FORMAT = '%M:%S'
|
13
|
+
class Logging:
|
14
|
+
|
15
|
+
def __init__(self, target=None, log_level: int = None, log_format=None, log_to_console=False, date_format=None):
|
16
|
+
self.target = target
|
17
|
+
self.log_level = log_level or DEFAULT_LOG_LEVEL
|
18
|
+
self.log_format = log_format or DEFAULT_LOG_FORMAT
|
19
|
+
self.date_format = date_format or DEFAULT_DATE_FORMAT
|
20
|
+
if log_to_console:
|
21
|
+
self.log_to_sys_stdout()
|
22
|
+
|
23
|
+
def __enter__(self): return self
|
24
|
+
def __exit__ (self, exc_type, exc_val, exc_tb): pass
|
25
|
+
|
26
|
+
def add_stream_handler(self, stream):
|
27
|
+
stream_handler = logging.StreamHandler(stream=stream)
|
28
|
+
self.logger().addHandler(stream_handler)
|
29
|
+
self.set_logger_level()
|
30
|
+
self.set_format(stream_handler)
|
31
|
+
|
32
|
+
return stream_handler
|
33
|
+
|
34
|
+
@cache_on_self
|
35
|
+
def logger(self):
|
36
|
+
if self.target is not None:
|
37
|
+
if inspect.isclass(self.target) or inspect.ismodule(self.target):
|
38
|
+
self.target = self.target.__name__
|
39
|
+
return logging.getLogger(self.target)
|
40
|
+
|
41
|
+
def enable_log_to_console(self, log_level=logging.INFO):
|
42
|
+
self.log_to_sys_stdout()
|
43
|
+
self.set_logger_level(log_level)
|
44
|
+
return self
|
45
|
+
|
46
|
+
def enable_pycharm_logging(self):
|
47
|
+
if self.is_pycharm_running():
|
48
|
+
self.log_to_sys_stdout()
|
49
|
+
return self
|
50
|
+
|
51
|
+
def is_pycharm_running(self) -> bool:
|
52
|
+
first_arg = sys.argv[0]
|
53
|
+
return ('docrunner.py' in first_arg) or ('pytest_runner.py' in first_arg)
|
54
|
+
|
55
|
+
def log_to_sys_stdout(self):
|
56
|
+
return self.add_stream_handler(sys.stdout)
|
57
|
+
|
58
|
+
def log_to_string_io(self):
|
59
|
+
log_stream = StringIO()
|
60
|
+
return self.add_stream_handler(log_stream)
|
61
|
+
|
62
|
+
|
63
|
+
def set_format(self, stream_handler):
|
64
|
+
formatter = logging.Formatter(fmt=self.log_format, datefmt=self.date_format)
|
65
|
+
stream_handler.setFormatter(formatter)
|
66
|
+
return formatter
|
67
|
+
|
68
|
+
def set_format_on_all_handlers(self):
|
69
|
+
for handler in logging.root.handlers[:]:
|
70
|
+
handler.setFormatter(logging.Formatter(fmt=self.log_format, datefmt=self.date_format))
|
71
|
+
return self
|
72
|
+
|
73
|
+
def set_logger_level(self, level=None):
|
74
|
+
self.logger().setLevel(level or self.log_level)
|
75
|
+
|
76
|
+
|
77
|
+
def info (self,message, *args, **kwargs): self.logger().info (message, *args, **kwargs)
|
78
|
+
def warning (self,message, *args, **kwargs): self.logger().warning (message, *args, **kwargs)
|
79
|
+
def debug (self,message, *args, **kwargs): self.logger().debug (message, *args, **kwargs)
|
80
|
+
def error (self,message, *args, **kwargs): self.logger().error (message, *args, **kwargs)
|
81
|
+
def critical(self,message, *args, **kwargs): self.logger().critical (message, *args, **kwargs)
|
@@ -0,0 +1,52 @@
|
|
1
|
+
from unittest.mock import patch, MagicMock, _patch
|
2
|
+
from osbot_utils.utils.Dev import pprint
|
3
|
+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
|
4
|
+
|
5
|
+
|
6
|
+
class Patch_Print(Kwargs_To_Self):
|
7
|
+
enabled : bool = True
|
8
|
+
expected_calls : list
|
9
|
+
mocked_print : MagicMock
|
10
|
+
patched_print : _patch
|
11
|
+
print_calls : bool
|
12
|
+
|
13
|
+
|
14
|
+
def __init__(self, **kwargs):
|
15
|
+
super().__init__(**kwargs)
|
16
|
+
|
17
|
+
def __enter__(self):
|
18
|
+
if self.enabled:
|
19
|
+
self.patched_print = patch('builtins.print')
|
20
|
+
self.mocked_print = self.patched_print.start()
|
21
|
+
return self
|
22
|
+
|
23
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
24
|
+
if self.enabled:
|
25
|
+
self.patched_print.stop()
|
26
|
+
|
27
|
+
if self.print_calls:
|
28
|
+
pprint(self.calls())
|
29
|
+
#print(self.mocked_print.call_args_list)
|
30
|
+
|
31
|
+
if self.expected_calls:
|
32
|
+
assert self.calls() == self.expected_calls
|
33
|
+
|
34
|
+
def call_args_list(self):
|
35
|
+
if self.mocked_print:
|
36
|
+
return self.mocked_print.call_args_list
|
37
|
+
return []
|
38
|
+
|
39
|
+
def calls(self):
|
40
|
+
calls_data = []
|
41
|
+
if self.mocked_print:
|
42
|
+
for call in self.mocked_print.call_args_list:
|
43
|
+
if len(call.args) == 0 and call.kwargs == {}:
|
44
|
+
call_data = ''
|
45
|
+
elif len(call.args) == 1 and call.kwargs == {}:
|
46
|
+
call_data = call.args[0]
|
47
|
+
else:
|
48
|
+
call_data = (call.args, call.kwargs)
|
49
|
+
calls_data.append(call_data)
|
50
|
+
return calls_data
|
51
|
+
|
52
|
+
|
@@ -0,0 +1,89 @@
|
|
1
|
+
import sys
|
2
|
+
|
3
|
+
# see https://explog.in/notes/settrace.html for ideas on how to expand this class
|
4
|
+
|
5
|
+
class Profiler:
|
6
|
+
|
7
|
+
def __init__(self):
|
8
|
+
self.events = []
|
9
|
+
self.profile_options = self.default_profile_options()
|
10
|
+
self.previous_profiler = self.current_profiler()
|
11
|
+
self.on_event = None
|
12
|
+
|
13
|
+
def __enter__(self):
|
14
|
+
sys.setprofile(self.profiling_function)
|
15
|
+
return self
|
16
|
+
|
17
|
+
def __exit__(self, exception_type, exception_value, exception_traceback):
|
18
|
+
sys.setprofile(self.previous_profiler)
|
19
|
+
|
20
|
+
def add_values(self, profile_options, source):
|
21
|
+
item = {}
|
22
|
+
for arg_name in set(profile_options):
|
23
|
+
option = profile_options.get(arg_name)
|
24
|
+
value = getattr(source, arg_name)
|
25
|
+
if type(option) is dict:
|
26
|
+
item[arg_name] = self.add_values(option, value)
|
27
|
+
else:
|
28
|
+
if profile_options.get(arg_name):
|
29
|
+
if arg_name == 'f_locals':
|
30
|
+
item[arg_name] = value.copy() # create a copy of the var
|
31
|
+
else:
|
32
|
+
item[arg_name] = value
|
33
|
+
return item
|
34
|
+
|
35
|
+
def current_profiler(self):
|
36
|
+
return sys.getprofile()
|
37
|
+
|
38
|
+
def default_profile_options(self):
|
39
|
+
return {
|
40
|
+
'f_back' : False,
|
41
|
+
'f_builtins' : False,
|
42
|
+
'f_code' : {
|
43
|
+
'co_argcount' : True ,
|
44
|
+
'co_cellvars' : True ,
|
45
|
+
'co_code' : True ,
|
46
|
+
'co_consts' : True ,
|
47
|
+
'co_filename' : True ,
|
48
|
+
'co_firstlineno' : True ,
|
49
|
+
'co_flags' : True ,
|
50
|
+
'co_freevars' : True ,
|
51
|
+
'co_kwonlyargcount' : True ,
|
52
|
+
'co_lnotab' : True ,
|
53
|
+
'co_name' : True ,
|
54
|
+
'co_names' : True ,
|
55
|
+
'co_nlocals' : True ,
|
56
|
+
'co_posonlyargcount': True ,
|
57
|
+
'co_stacksize' : True ,
|
58
|
+
'co_varnames' : True ,
|
59
|
+
} ,
|
60
|
+
'f_globals' : False,
|
61
|
+
'f_lasti' : True ,
|
62
|
+
'f_lineno' : True ,
|
63
|
+
'f_locals' : True ,
|
64
|
+
'f_trace' : True ,
|
65
|
+
'f_trace_lines' : True ,
|
66
|
+
'f_trace_opcodes': True
|
67
|
+
}
|
68
|
+
|
69
|
+
def get_last_event(self):
|
70
|
+
return self.events.pop()
|
71
|
+
|
72
|
+
def get_f_locals(self):
|
73
|
+
return self.get_last_event().get('f_locals')
|
74
|
+
|
75
|
+
def get_f_locals_variable(self, var_name):
|
76
|
+
return self.get_f_locals().get(var_name)
|
77
|
+
|
78
|
+
def profiling_function(self, frame, event, arg):
|
79
|
+
if type(frame.f_locals.get('self')) != Profiler: # dont' capture traces of the current (Trace) class
|
80
|
+
item = self.add_values(self.profile_options, frame)
|
81
|
+
item['arg' ] = arg
|
82
|
+
item['event'] = event
|
83
|
+
self.events.append(item)
|
84
|
+
if self.on_event:
|
85
|
+
self.on_event(self,frame, event, arg) # allow the caler to see and modify the data (after its data been captured)
|
86
|
+
|
87
|
+
def set_on_event(self, on_event):
|
88
|
+
self.on_event = on_event
|
89
|
+
return self
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import io
|
2
|
+
from contextlib import redirect_stderr
|
3
|
+
|
4
|
+
|
5
|
+
class Stderr:
|
6
|
+
def __init__(self):
|
7
|
+
self.output = io.StringIO()
|
8
|
+
self.redirect_stderr = redirect_stderr(self.output)
|
9
|
+
|
10
|
+
def __enter__(self):
|
11
|
+
self.redirect_stderr.__enter__()
|
12
|
+
return self
|
13
|
+
|
14
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
15
|
+
self.redirect_stderr.__exit__(exc_type, exc_val, exc_tb)
|
16
|
+
|
17
|
+
def value(self):
|
18
|
+
return self.output.getvalue()
|
19
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import io
|
2
|
+
from contextlib import redirect_stdout
|
3
|
+
|
4
|
+
|
5
|
+
class Stdout:
|
6
|
+
def __init__(self):
|
7
|
+
self.output = io.StringIO()
|
8
|
+
self.redirect_stdout = redirect_stdout(self.output)
|
9
|
+
|
10
|
+
def __enter__(self):
|
11
|
+
self.redirect_stdout.__enter__()
|
12
|
+
return self
|
13
|
+
|
14
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
15
|
+
self.redirect_stdout.__exit__(exc_type, exc_val, exc_tb)
|
16
|
+
|
17
|
+
def value(self):
|
18
|
+
return self.output.getvalue()
|
19
|
+
|
@@ -0,0 +1,46 @@
|
|
1
|
+
from osbot_utils.utils.Files import Files, file_delete, folder_delete_all, files_list, file_create, file_name, \
|
2
|
+
parent_folder, file_exists, file_contents
|
3
|
+
from osbot_utils.utils.Misc import random_filename
|
4
|
+
|
5
|
+
|
6
|
+
class Temp_File:
|
7
|
+
def __init__(self, contents='...', extension='tmp'):
|
8
|
+
self.tmp_file = random_filename(extension)
|
9
|
+
self.tmp_folder = None
|
10
|
+
self.file_path = None
|
11
|
+
self.original_contents = contents
|
12
|
+
|
13
|
+
def __enter__(self):
|
14
|
+
self.tmp_folder = Files.temp_folder(prefix='temp_folder_')
|
15
|
+
self.file_path = Files.path_combine(self.tmp_folder, self.tmp_file)
|
16
|
+
file_create(self.file_path, self.original_contents)
|
17
|
+
return self
|
18
|
+
|
19
|
+
def __exit__(self, type, value, traceback):
|
20
|
+
file_delete (self.file_path)
|
21
|
+
folder_delete_all(self.tmp_folder)
|
22
|
+
|
23
|
+
def contents(self):
|
24
|
+
return file_contents(self.file_path)
|
25
|
+
|
26
|
+
def delete(self):
|
27
|
+
return file_delete(self.file_path)
|
28
|
+
|
29
|
+
def exists(self):
|
30
|
+
return file_exists(self.file_path)
|
31
|
+
|
32
|
+
def file_name(self):
|
33
|
+
return file_name(self.path())
|
34
|
+
|
35
|
+
def files_in_folder(self):
|
36
|
+
return files_list(self.tmp_folder)
|
37
|
+
|
38
|
+
def folder(self):
|
39
|
+
return parent_folder(self.path())
|
40
|
+
|
41
|
+
def path(self):
|
42
|
+
return self.file_path
|
43
|
+
|
44
|
+
def write(self, contents):
|
45
|
+
file_create(self.file_path, contents)
|
46
|
+
return self
|