osbot-utils 1.16.0__py3-none-any.whl → 1.20.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. osbot_utils/base_classes/Kwargs_To_Self.py +3 -54
  2. osbot_utils/base_classes/Type_Safe.py +6 -0
  3. osbot_utils/context_managers/disable_root_loggers.py +30 -0
  4. osbot_utils/helpers/CFormat.py +147 -0
  5. osbot_utils/helpers/CPrint.py +5 -50
  6. osbot_utils/helpers/Print_Table.py +1 -1
  7. osbot_utils/helpers/cache_requests/Cache__Requests__Actions.py +23 -0
  8. osbot_utils/helpers/cache_requests/Cache__Requests__Config.py +32 -0
  9. osbot_utils/helpers/cache_requests/Cache__Requests__Data.py +105 -0
  10. osbot_utils/helpers/cache_requests/Cache__Requests__Invoke.py +55 -0
  11. osbot_utils/helpers/cache_requests/Cache__Requests__Row.py +64 -0
  12. osbot_utils/helpers/cache_requests/Cache__Requests__Table.py +16 -0
  13. osbot_utils/helpers/cache_requests/__init__.py +0 -0
  14. osbot_utils/helpers/cache_requests/flows/flow__Cache__Requests.py +11 -0
  15. osbot_utils/helpers/flows/Flow.py +145 -0
  16. osbot_utils/helpers/flows/Task.py +18 -0
  17. osbot_utils/helpers/flows/__init__.py +0 -0
  18. osbot_utils/helpers/sqlite/{domains/schemas → cache}/Schema__Table__Requests.py +6 -4
  19. osbot_utils/helpers/sqlite/cache/Sqlite__Cache__Requests.py +104 -0
  20. osbot_utils/helpers/sqlite/{domains → cache}/Sqlite__Cache__Requests__Patch.py +10 -8
  21. osbot_utils/helpers/sqlite/cache/Sqlite__Cache__Requests__Sqlite.py +18 -0
  22. osbot_utils/helpers/sqlite/cache/Sqlite__Cache__Requests__Table.py +48 -0
  23. osbot_utils/helpers/sqlite/{domains → cache}/Sqlite__DB__Requests.py +8 -7
  24. osbot_utils/helpers/sqlite/cache/TestCase__Sqlite__Cache__Requests.py +35 -0
  25. osbot_utils/helpers/sqlite/cache/__init__.py +0 -0
  26. osbot_utils/helpers/sqlite/domains/Sqlite__DB__Local.py +6 -2
  27. osbot_utils/helpers/{SCP.py → ssh/SCP.py} +23 -20
  28. osbot_utils/helpers/ssh/SSH.py +30 -0
  29. osbot_utils/helpers/ssh/SSH__Cache__Requests.py +66 -0
  30. osbot_utils/helpers/ssh/SSH__Execute.py +158 -0
  31. osbot_utils/helpers/ssh/SSH__Health_Check.py +49 -0
  32. osbot_utils/helpers/ssh/SSH__Linux.py +106 -0
  33. osbot_utils/helpers/ssh/SSH__Python.py +48 -0
  34. osbot_utils/helpers/ssh/TestCase__SSH.py +50 -0
  35. osbot_utils/helpers/ssh/__init__.py +0 -0
  36. osbot_utils/helpers/trace/Trace_Call__Print_Lines.py +1 -1
  37. osbot_utils/testing/Logging.py +15 -5
  38. osbot_utils/testing/Pytest.py +18 -0
  39. osbot_utils/utils/Env.py +29 -9
  40. osbot_utils/utils/Json.py +2 -9
  41. osbot_utils/utils/Misc.py +17 -16
  42. osbot_utils/utils/Objects.py +17 -7
  43. osbot_utils/utils/Python_Logger.py +54 -38
  44. osbot_utils/utils/Str.py +20 -3
  45. osbot_utils/utils/Toml.py +33 -0
  46. osbot_utils/version +1 -1
  47. {osbot_utils-1.16.0.dist-info → osbot_utils-1.20.0.dist-info}/METADATA +2 -2
  48. {osbot_utils-1.16.0.dist-info → osbot_utils-1.20.0.dist-info}/RECORD +50 -23
  49. osbot_utils/helpers/SSH.py +0 -151
  50. osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests.py +0 -214
  51. {osbot_utils-1.16.0.dist-info → osbot_utils-1.20.0.dist-info}/LICENSE +0 -0
  52. {osbot_utils-1.16.0.dist-info → osbot_utils-1.20.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,145 @@
1
+ import logging
2
+ import typing
3
+ from functools import wraps
4
+
5
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
6
+ from osbot_utils.helpers.CFormat import CFormat, f_dark_grey, f_magenta
7
+ from osbot_utils.testing.Stdout import Stdout
8
+ from osbot_utils.utils.Misc import random_id, lower
9
+ from osbot_utils.utils.Python_Logger import Python_Logger
10
+ from osbot_utils.utils.Str import ansis_to_texts
11
+
12
+ FLOW__RANDOM_ID__PREFIX = 'flow_id__'
13
+ FLOW__RANDOM_NAME__PREFIX = 'flow_name__'
14
+ FLOW__LOGGING__LOG_FORMAT = '%(asctime)s.%(msecs)03d | %(levelname)-8s | %(message)s'
15
+ FLOW__LOGGING__DATE_FORMAT = '%H:%M:%S'
16
+
17
+ def flow(**flow_kwargs):
18
+
19
+ def decorator(function):
20
+ @wraps(function)
21
+ def wrapper(*args, **kwargs):
22
+ with Flow(**flow_kwargs) as flow:
23
+ flow.set_flow_target(function)
24
+ flow.setup()
25
+ flow.create_flow()
26
+ flow.execute_flow(*args, **kwargs)
27
+ return flow.return_value
28
+
29
+ return wrapper
30
+ return decorator
31
+
32
+
33
+ class Flow(Type_Safe):
34
+ captured_exec_logs : list
35
+ flow_id : str
36
+ flow_name : str
37
+ flow_target : callable
38
+ logger : Python_Logger
39
+ cformat : CFormat
40
+ log_to_console : bool = False
41
+ log_to_memory : bool = True
42
+ print_logs : bool = False
43
+ return_value : typing.Any
44
+
45
+
46
+ def config_logger(self):
47
+ with self.logger as _:
48
+ _.set_log_level(logging.DEBUG)
49
+ _.set_log_format(log_format=FLOW__LOGGING__LOG_FORMAT, date_format=FLOW__LOGGING__DATE_FORMAT)
50
+ if self.log_to_console:
51
+ _.add_console_logger()
52
+
53
+
54
+ def debug(self, message):
55
+ self.logger.debug(message)
56
+
57
+ def create_flow(self):
58
+ self.set_flow_name()
59
+ self.debug(f"Created flow run '{self.f__flow_id()}' for flow '{self.f__flow_name()}'")
60
+
61
+ def execute_flow(self, *args, **kwargs):
62
+ if self.log_to_memory:
63
+ self.logger.add_memory_logger() # todo: move to method that does pre-execute tasks
64
+
65
+ self.debug(f"Executing flow run '{self.f__flow_id()}''")
66
+ try:
67
+ with Stdout() as stdout:
68
+ self.return_value = self.flow_target(*args, **kwargs) # todo, capture *args, **kwargs in logs
69
+ except Exception as error:
70
+ self.logger.error(self.cformat.red(f"Error executing flow: {error}"))
71
+ self.log_captured_stdout(stdout)
72
+ self.debug(f"{f_dark_grey('return value')}: {self.return_value}")
73
+ self.debug(f"Finished flow run '{self.f__flow_id()}''")
74
+
75
+ if self.log_to_memory:
76
+ self.captured_exec_logs = self.log_messages_with_colors()
77
+ self.logger.remove_memory_logger() # todo: move to method that does post-execute tasks
78
+
79
+ def f__flow_id(self):
80
+ return self.cformat.green(self.flow_id)
81
+
82
+ def f__flow_name(self):
83
+ return self.cformat.blue(self.flow_name)
84
+
85
+ def info(self, message):
86
+ self.logger.info(message)
87
+
88
+ def log_captured_stdout(self, stdout):
89
+ for line in stdout.value().splitlines():
90
+ if line:
91
+ self.info(f_magenta(line))
92
+ if self.print_logs:
93
+ print()
94
+ print()
95
+ self.print_log_messages()
96
+
97
+
98
+ def log_messages(self):
99
+ return ansis_to_texts(self.log_messages_with_colors())
100
+
101
+ def log_messages_with_colors(self):
102
+ return self.logger.memory_handler_messages()
103
+
104
+ def print_log_messages(self, use_colors=True):
105
+ if use_colors:
106
+ for message in self.logger.memory_handler_messages():
107
+ print(message)
108
+ else:
109
+ for message in self.log_messages():
110
+ print(message)
111
+ return self
112
+ def random_flow_id(self):
113
+ return lower(random_id(prefix=FLOW__RANDOM_ID__PREFIX))
114
+
115
+ def random_flow_name(self):
116
+ return lower(random_id(prefix=FLOW__RANDOM_NAME__PREFIX))
117
+
118
+
119
+ def set_flow_target(self, target):
120
+ self.flow_target = target
121
+ return self
122
+
123
+ def set_flow_name(self, value=None):
124
+ if value:
125
+ self.flow_name = value
126
+ else:
127
+ if not self.flow_name:
128
+ if hasattr(self.flow_target, '__name__'):
129
+ self.flow_name = self.flow_target.__name__
130
+ else:
131
+ self.flow_name = self.random_flow_name()
132
+ def setup(self):
133
+ with self as _:
134
+ _.cformat.auto_bold = True
135
+ _.config_logger()
136
+ _.setup_flow_run()
137
+ return self
138
+
139
+ def setup_flow_run(self):
140
+ with self as _:
141
+ if not _.flow_id:
142
+ _.flow_id = self.random_flow_id()
143
+ #if not _.flow_name:
144
+ # _.flow_name = self.flow_target.__name__
145
+ #self.random_flow_name()
@@ -0,0 +1,18 @@
1
+ import inspect
2
+
3
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
4
+ from osbot_utils.helpers.flows.Flow import Flow
5
+
6
+
7
+ class Task(Type_Safe):
8
+ task_id : str
9
+ task_name : str
10
+
11
+ def find_flow(self):
12
+ stack = inspect.stack()
13
+ for frame_info in stack:
14
+ frame = frame_info.frame
15
+ if 'self' in frame.f_locals:
16
+ instance = frame.f_locals['self']
17
+ if type(instance) is Flow:
18
+ return instance
File without changes
@@ -1,12 +1,14 @@
1
1
  from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
2
2
 
3
3
  class Schema__Table__Requests(Kwargs_To_Self):
4
+ comments : str
5
+ metadata : str
6
+ request_type : str
4
7
  request_hash : str
5
8
  request_data : str
9
+ response_bytes: bytes
6
10
  response_hash : str
7
11
  response_data : str
8
- response_bytes: bytes
9
- cache_hits : int # todo: to implement
12
+ response_type : str
13
+ source : str
10
14
  timestamp : int
11
- latest : bool # todo: add native bool support to sqlite
12
- comments : str
@@ -0,0 +1,104 @@
1
+ import types
2
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
3
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Actions import Cache__Requests__Actions
4
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Config import Cache__Requests__Config
5
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Data import Cache__Requests__Data
6
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Invoke import Cache__Requests__Invoke
7
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Row import Cache__Requests__Row
8
+ from osbot_utils.helpers.sqlite.cache.Sqlite__Cache__Requests__Table import Sqlite__Cache__Requests__Table
9
+ from osbot_utils.helpers.sqlite.cache.Sqlite__Cache__Requests__Sqlite import Sqlite__Cache__Requests__Sqlite
10
+
11
+
12
+ class Sqlite__Cache__Requests(Type_Safe):
13
+ db_path : None
14
+ db_name : None
15
+ table_name : None
16
+
17
+ def __init__(self, **kwargs):
18
+ super().__init__(**kwargs)
19
+
20
+ # todo refactor this whole section to a DI (DependencyInjection / Type_Registry class, which is the one responsible for creating these objects in the right order of dependency)
21
+
22
+ self.cache_config = Cache__Requests__Config()
23
+ self.config = self.cache_config
24
+
25
+ kwargs__cache_sqlite = dict(config=self.cache_config, db_path=self.db_path, db_name=self.db_name, table_name=self.table_name)
26
+ self.cache_sqlite = Sqlite__Cache__Requests__Sqlite (**kwargs__cache_sqlite)
27
+ self.sqlite_requests = self.cache_sqlite.sqlite_requests
28
+
29
+ #self.cache_table = self.cache_sqlite.cache_table
30
+ self.cache_table = Sqlite__Cache__Requests__Table(cache_table=self.cache_sqlite.cache_table())
31
+
32
+
33
+ kwargs__cache_table = dict( cache_table = self.cache_table )
34
+
35
+ kwargs__cache_data = dict(**kwargs__cache_table, cache_request_data = self.cache_request_data ,
36
+ config = self.cache_config )
37
+ self.cache_data = Cache__Requests__Data(**kwargs__cache_data)
38
+
39
+ kwargs__cache_row = dict(**kwargs__cache_table, config = self.cache_config )
40
+ self.cache_row = Cache__Requests__Row (**kwargs__cache_row )
41
+
42
+
43
+
44
+ kwargs__cache_actions = dict(**kwargs__cache_table, cache_row=self.cache_row)
45
+ self.cache_actions = Cache__Requests__Actions(**kwargs__cache_actions )
46
+
47
+ kwargs__cache_invoke = dict(cache_data = self.cache_data ,
48
+ cache_actions = self.cache_actions ,
49
+ config = self.cache_config )
50
+ self.cache_invoke = Cache__Requests__Invoke(**kwargs__cache_invoke)
51
+
52
+ self.apply_refactoring_patches()
53
+
54
+ def apply_refactoring_patches(self):
55
+ self.cache_add = self.cache_actions.cache_add
56
+ self.cache_delete = self.cache_actions.cache_delete
57
+ self.create_new_cache_row_data = self.cache_actions.create_new_cache_row_data
58
+
59
+ self.cache_entries = self.cache_data.cache_entries
60
+ self.cache_entry = self.cache_data.cache_entry
61
+ self.cache_entry_comments = self.cache_data.cache_entry_comments
62
+ self.cache_entry_comments_update = self.cache_data.cache_entry_comments_update
63
+ self.cache_entry_for_request_params = self.cache_data.cache_entry_for_request_params
64
+ self.response_data_for__request_hash = self.cache_data.response_data_for__request_hash
65
+ self.response_data__all = self.cache_data.response_data__all
66
+ self.response_data_deserialize = self.cache_data.response_data_deserialize
67
+ self.response_data_serialize = self.cache_data.response_data_serialize
68
+
69
+ self.create_new_cache_obj = self.cache_row.create_new_cache_obj
70
+
71
+ self.cache_table__clear = self.cache_table.cache_table__clear
72
+ self.delete_where_request_data = self.cache_table.delete_where_request_data
73
+ self.rows_where = self.cache_table.rows_where
74
+ self.rows_where__request_data = self.cache_table.rows_where__request_data
75
+ self.rows_where__request_hash = self.cache_table.rows_where__request_hash
76
+
77
+ self.disable = self.cache_config.disable
78
+ self.enable = self.cache_config.enable
79
+ self.only_from_cache = self.cache_config.only_from_cache
80
+ self.update = self.cache_config.update
81
+ self.set__add_timestamp = self.cache_config.set__add_timestamp
82
+
83
+ self.invoke = self.cache_invoke.invoke
84
+ self.invoke_target = self.cache_invoke.invoke_target
85
+ #self.invoke_with_cache = self.cache_invoke.invoke_with_cache
86
+ self.invoke_target__and_add_to_cache = self.cache_invoke.invoke_target__and_add_to_cache
87
+ self.transform_raw_response = self.cache_invoke.transform_raw_response
88
+
89
+
90
+
91
+ # FOR NOW: these methods cannot be refactored
92
+
93
+ # this is the method that is current overwritten to create custom request data
94
+ def cache_request_data(self, *args, **target_kwargs):
95
+ return {'args': list(args), 'kwargs': target_kwargs} # convert the args tuple to a list since that is what it will be once it is serialised
96
+
97
+ def invoke_with_cache(self, *args, **target_kwargs):
98
+ return self.cache_invoke.invoke_with_cache(*args, **target_kwargs)
99
+
100
+ def set_on_invoke_target(self, on_invoke_target : types.FunctionType):
101
+ self.cache_invoke.on_invoke_target = on_invoke_target
102
+
103
+ def requests_data__all(self): # currently overwritten by Bedrock__Cache
104
+ return self.cache_data.requests_data__all()
@@ -1,20 +1,19 @@
1
1
  import types
2
2
 
3
- from osbot_utils.helpers.sqlite.domains.Sqlite__Cache__Requests import Sqlite__Cache__Requests
4
- from osbot_utils.utils.Dev import pprint
5
- from osbot_utils.utils.Misc import random_text
3
+ from osbot_utils.helpers.sqlite.cache.Sqlite__Cache__Requests import Sqlite__Cache__Requests
4
+ from osbot_utils.utils.Misc import random_text
6
5
 
7
6
 
8
7
  class Sqlite__Cache__Requests__Patch(Sqlite__Cache__Requests):
9
- db_name : str = random_text('requests_cache_')
10
- table_name : str = random_text('requests_table_')
11
- pickle_response : bool = True
8
+ db_name : str #= random_text('requests_cache_')
9
+ table_name : str #= random_text('requests_table_') # todo : remove this so that we default to an in memory db
12
10
  target_function : types.FunctionType
13
11
  target_class : object
14
12
  target_function_name: str
15
13
 
16
- def __init__(self, db_path=None):
17
- super().__init__(db_path=db_path, db_name=self.db_name, table_name=self.table_name)
14
+ def __init__(self, db_name=None, table_name=None, db_path=None):
15
+ super().__init__(db_path=db_path, db_name=db_name, table_name=table_name)
16
+ self.cache_config.pickle_response = True
18
17
 
19
18
  def __enter__(self):
20
19
  self.patch_apply()
@@ -24,6 +23,9 @@ class Sqlite__Cache__Requests__Patch(Sqlite__Cache__Requests):
24
23
  self.patch_restore()
25
24
  return
26
25
 
26
+ def database(self):
27
+ return self.cache_table.database
28
+
27
29
  def delete(self):
28
30
  return self.sqlite_requests.delete()
29
31
 
@@ -0,0 +1,18 @@
1
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
2
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Config import Cache__Requests__Config
3
+ from osbot_utils.helpers.sqlite.cache.Sqlite__DB__Requests import Sqlite__DB__Requests
4
+
5
+
6
+ class Sqlite__Cache__Requests__Sqlite(Type_Safe):
7
+ sqlite_requests : Sqlite__DB__Requests = None
8
+ config : Cache__Requests__Config
9
+ db_path : str
10
+ db_name : str
11
+ table_name : str
12
+ def __init__(self, **kwargs):
13
+ super().__init__(**kwargs)
14
+ self.sqlite_requests = Sqlite__DB__Requests(db_path=self.db_path, db_name=self.db_name, table_name=self.table_name)
15
+
16
+ def cache_table(self):
17
+ return self.sqlite_requests.table_requests()
18
+
@@ -0,0 +1,48 @@
1
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Table import Cache__Requests__Table
2
+ from osbot_utils.helpers.sqlite.Sqlite__Table import Sqlite__Table
3
+ from osbot_utils.utils.Json import json_dumps
4
+
5
+
6
+ class Sqlite__Cache__Requests__Table(Cache__Requests__Table):
7
+ cache_table : Sqlite__Table
8
+
9
+ def __init__(self, **kwargs):
10
+ super().__init__( **kwargs)
11
+
12
+ self.table_name = self.cache_table.table_name
13
+ self._table_create = self.cache_table._table_create
14
+ self.database = self.cache_table.database
15
+ self.clear = self.cache_table.clear
16
+ self.exists = self.cache_table.exists
17
+ self.indexes = self.cache_table.indexes
18
+ self.new_row_obj = self.cache_table.new_row_obj
19
+ self.row_add_and_commit = self.cache_table.row_add_and_commit
20
+ self.row_update = self.cache_table.row_update
21
+ self.row_schema = self.cache_table.row_schema
22
+ self.rows = self.cache_table.rows
23
+ self.rows_delete_where = self.cache_table.rows_delete_where
24
+ self.schema__by_name_type = self.cache_table.schema__by_name_type
25
+ self.select_rows_where = self.cache_table.select_rows_where
26
+ self.size = self.cache_table.size
27
+
28
+ def cache_table__clear(self):
29
+ return self.cache_table.clear()
30
+
31
+ def delete_where_request_data(self, request_data): # todo: check if it is ok to use the request_data as a query target, or if we should use the request_hash variable
32
+ if type(request_data) is dict: # if we get an request_data obj
33
+ request_data = json_dumps(request_data) # convert it to the json dump
34
+ if type(request_data) is str: # make sure we have a string
35
+ if len(self.rows_where__request_data(request_data)) > 0: # make sure there is at least one entry to delete
36
+ self.cache_table.rows_delete_where(request_data=request_data) # delete it
37
+ return len(self.rows_where__request_data(request_data)) == 0 # confirm it was deleted
38
+ return False # if anything was not right, return False
39
+
40
+ def rows_where(self, **kwargs):
41
+ return self.cache_table.select_rows_where(**kwargs)
42
+
43
+ def rows_where__request_data(self, request_data):
44
+ return self.rows_where(request_data=request_data)
45
+
46
+ def rows_where__request_hash(self, request_hash):
47
+ return self.rows_where(request_hash=request_hash)
48
+
@@ -1,7 +1,6 @@
1
- from osbot_utils.decorators.methods.cache_on_self import cache_on_self
2
- from osbot_utils.helpers.sqlite.domains.Sqlite__DB__Local import Sqlite__DB__Local
3
- from osbot_utils.helpers.sqlite.domains.schemas.Schema__Table__Requests import Schema__Table__Requests
4
- from osbot_utils.utils.Misc import random_text
1
+ from osbot_utils.decorators.methods.cache_on_self import cache_on_self
2
+ from osbot_utils.helpers.sqlite.domains.Sqlite__DB__Local import Sqlite__DB__Local
3
+ from osbot_utils.helpers.sqlite.cache.Schema__Table__Requests import Schema__Table__Requests
5
4
 
6
5
  SQLITE_TABLE__REQUESTS = 'requests'
7
6
 
@@ -12,11 +11,13 @@ class Sqlite__DB__Requests(Sqlite__DB__Local):
12
11
  def __init__(self,db_path=None, db_name=None, table_name=None):
13
12
  self.table_name = table_name or SQLITE_TABLE__REQUESTS
14
13
  self.table_schema = Schema__Table__Requests
15
- super().__init__(db_path=db_path, db_name=db_name)
16
- # if not self.table_name:
17
- # self.table_name = 'temp_table'
14
+ in_memory = not (db_path or db_name)
15
+ super().__init__(db_path=db_path, db_name=db_name, in_memory=in_memory)
18
16
  self.setup()
19
17
 
18
+ def __repr__(self):
19
+ return f'{self.__class__.__name__} [ db_name={self.db_name} | table_name={self.table_name} | in_memory={self.in_memory} ]'
20
+
20
21
  @cache_on_self
21
22
  def table_requests(self):
22
23
  return self.table(self.table_name)
@@ -0,0 +1,35 @@
1
+ from unittest import TestCase
2
+
3
+ from osbot_utils.helpers.sqlite.cache.Sqlite__Cache__Requests import Sqlite__Cache__Requests
4
+ from osbot_utils.utils.Misc import random_text
5
+
6
+
7
+ class TestCase__Sqlite__Cache__Requests(TestCase):
8
+ sqlite_cache_requests : Sqlite__Cache__Requests
9
+
10
+ @classmethod
11
+ def setUpClass(cls):
12
+ cls.sqlite_cache_requests = Sqlite__Cache__Requests()
13
+ cls.sqlite_cache_requests.set__add_timestamp(False) # disabling timestamp since it complicates the test data verification below
14
+ assert cls.sqlite_cache_requests.sqlite_requests.in_memory is True # confirm we have an in-memory db
15
+
16
+ def tearDown(self):
17
+ self.sqlite_cache_requests.cache_table.clear()
18
+
19
+ def add_test_requests(self, count=10):
20
+ def invoke_target(*args, **target_kwargs):
21
+ return {'type' : 'response' ,
22
+ 'source' : 'add_test_requests.invoke_target',
23
+ 'request_args' : args ,
24
+ 'request_kwargs': target_kwargs }
25
+
26
+ for i in range(count):
27
+ an_key = random_text('an_key')
28
+ an_dict = {'the': random_text('random_request')}
29
+ target = invoke_target
30
+ target_args = ['abc']
31
+ target_kwargs = {'an_key': an_key, 'an_dict': an_dict}
32
+ response = self.sqlite_cache_requests.invoke(target, target_args, target_kwargs)
33
+ # todo add comments to the entry
34
+ #self.sqlite_cache_requests.cache_entry_comments_update()
35
+ #pprint(response)
File without changes
@@ -9,10 +9,14 @@ ENV_NAME_PATH_LOCAL_DBS = 'PATH_LOCAL_DBS'
9
9
  class Sqlite__DB__Local(Sqlite__Database):
10
10
  db_name: str
11
11
 
12
- def __init__(self, db_path=None, db_name=None):
12
+ def __init__(self, db_path=None, db_name=None, in_memory=False):
13
+
13
14
  if hasattr(self, 'db_name') is False:
14
15
  self.db_name = db_name or random_text('db_local') + '.sqlite'
15
- super().__init__(db_path=db_path or self.path_local_db())
16
+ if in_memory:
17
+ super().__init__()
18
+ else:
19
+ super().__init__(db_path=db_path or self.path_local_db()) # todo: add option to run this in memory, without creating a temp file
16
20
 
17
21
  def path_db_folder(self):
18
22
  return environ.get(ENV_NAME_PATH_LOCAL_DBS) or current_temp_folder()
@@ -1,14 +1,13 @@
1
- from osbot_utils.context_managers.capture_duration import capture_duration
2
- from osbot_utils.helpers.SSH import SSH
3
- from osbot_utils.testing.Temp_Zip import Temp_Zip
4
- from osbot_utils.utils.Dev import pprint
5
- from osbot_utils.utils.Files import file_exists, file_not_exists, file_name
6
- from osbot_utils.utils.Process import start_process
7
- from osbot_utils.utils.Status import status_error
8
- from osbot_utils.utils.Zip import zip_folder
1
+ from osbot_utils.context_managers.capture_duration import capture_duration
2
+ from osbot_utils.helpers.ssh.SSH__Execute import SSH__Execute
3
+ from osbot_utils.testing.Temp_Zip import Temp_Zip
4
+ from osbot_utils.utils.Files import file_not_exists, file_name
5
+ from osbot_utils.utils.Process import start_process
6
+ from osbot_utils.utils.Status import status_error
9
7
 
10
8
 
11
- class SCP(SSH):
9
+
10
+ class SCP(SSH__Execute):
12
11
 
13
12
  def copy_file_to_host(self, local_file, host_file=None):
14
13
  if file_not_exists(local_file):
@@ -31,20 +30,24 @@ class SCP(SSH):
31
30
  if file_not_exists(local_folder):
32
31
  return status_error(error="in copy_folder_as_zip_to_host, local_folder provided doesn't exist in current host", data={'local_folder':local_folder})
33
32
  with Temp_Zip(target=local_folder) as temp_zip:
34
- host_file = temp_zip.file_name()
35
- kwargs = dict(local_file = temp_zip.path(),
36
- host_file = host_file )
37
- self.mkdir(unzip_to_folder)
38
- self.copy_file_to_host(**kwargs)
39
- command = f'unzip {host_file} -d {unzip_to_folder}'
40
- self.execute_command(command)
41
- self.rm(host_file)
42
-
43
-
44
-
33
+ host_file = temp_zip.file_name()
34
+ kwargs = dict(local_file = temp_zip.path(),
35
+ host_file = host_file )
36
+ command_unzip = f'unzip {host_file} -d {unzip_to_folder}'
37
+ result_ssh_mkdir = self.mkdir(unzip_to_folder)
38
+ result_scp_zip = self.copy_file_to_host(**kwargs)
39
+ result_ssh_unzip = self.execute_command(command_unzip)
40
+ result_rm_zip = self.rm(host_file)
41
+ return dict(result_ssh_mkdir=result_ssh_mkdir ,
42
+ result_scp_zip =result_scp_zip ,
43
+ result_ssh_unzip=result_ssh_unzip ,
44
+ result_rm_zip =result_rm_zip )
45
45
 
46
46
  def execute_scp_command(self, scp_args):
47
47
  if self.ssh_host and self.ssh_key_file and self.ssh_key_user and scp_args:
48
+ if scp_args[0] == '-p': # todo refactor this to a better method/class to create the ssh and scp args
49
+ scp_args[0] = '-P' # this hack is to handle the fact that ssh and scp use different flags for the port!! WTF!! :)
50
+
48
51
  with capture_duration() as duration:
49
52
  result = start_process("scp", scp_args) # execute scp command using subprocess.run(...)
50
53
  result['duration'] = duration.data()
@@ -0,0 +1,30 @@
1
+ from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
2
+ from osbot_utils.decorators.methods.cache_on_self import cache_on_self
3
+ from osbot_utils.helpers.ssh.SCP import SCP
4
+ from osbot_utils.helpers.ssh.SSH__Execute import SSH__Execute
5
+ from osbot_utils.helpers.ssh.SSH__Linux import SSH__Linux
6
+ from osbot_utils.helpers.ssh.SSH__Python import SSH__Python
7
+
8
+ class SSH(Kwargs_To_Self):
9
+
10
+ def setup(self):
11
+ self.ssh_execute().setup()
12
+ return self
13
+
14
+ @cache_on_self
15
+ def scp(self):
16
+ kwargs = self.ssh_execute().__locals__() # get the current ssh config details
17
+ scp = SCP(**kwargs) # use it in the ctor of SCP
18
+ return scp
19
+
20
+ @cache_on_self
21
+ def ssh_execute(self):
22
+ return SSH__Execute()
23
+
24
+ @cache_on_self
25
+ def ssh_linux(self):
26
+ return SSH__Linux(ssh_execute = self.ssh_execute())
27
+
28
+ @cache_on_self
29
+ def ssh_python(self):
30
+ return SSH__Python(ssh_execute = self.ssh_execute(), ssh_linux = self.ssh_linux())
@@ -0,0 +1,66 @@
1
+ from osbot_utils.helpers.sqlite.cache.Sqlite__Cache__Requests__Patch import Sqlite__Cache__Requests__Patch
2
+ from osbot_utils.helpers.ssh.SSH__Execute import SSH__Execute
3
+ from osbot_utils.utils.Call_Stack import call_stack_frames_data
4
+ from osbot_utils.utils.Dev import pprint
5
+ from osbot_utils.utils.Json import json_to_str, json_loads
6
+ from osbot_utils.utils.Python_Logger import Python_Logger
7
+ from osbot_utils.utils.Toml import dict_to_toml, toml_to_dict
8
+
9
+ SQLITE_DB_NAME__SSH_REQUESTS_CACHE = 'ssh_requests_cache.sqlite'
10
+ SQLITE_TABLE_NAME__SSH_REQUESTS = 'ssh_requests'
11
+
12
+
13
+
14
+ class SSH__Cache__Requests(Sqlite__Cache__Requests__Patch):
15
+ db_path : str
16
+ db_name : str = SQLITE_DB_NAME__SSH_REQUESTS_CACHE
17
+ table_name : str = SQLITE_TABLE_NAME__SSH_REQUESTS
18
+ add_caller_signature_to_cache_key : bool = True
19
+ print_ssh_execution_stdout : bool = False
20
+ logging : Python_Logger
21
+
22
+ def __init__(self, **kwargs):
23
+ super().__init__(**kwargs)
24
+ self.target_class = SSH__Execute
25
+ self.target_function = SSH__Execute.execute_command
26
+ self.target_function_name = "execute_command"
27
+ self.print_requests = False
28
+ self.logging.add_console_logger()
29
+
30
+
31
+ def invoke_target(self, target, target_args, target_kwargs):
32
+ if self.print_requests:
33
+ print(f'[invoke_target]: {target_args}')
34
+ return super().invoke_target(target, target_args, target_kwargs)
35
+
36
+ def invoke_with_cache(self, **invoke_kwargs):
37
+ result = super().invoke_with_cache(**invoke_kwargs)
38
+ stdout = result.get('stdout')
39
+ if self.print_ssh_execution_stdout and stdout:
40
+ #print(stdout)
41
+ self.logging.info('\n\n' + stdout)
42
+ return result
43
+
44
+ def request_data(self, *args, **kwargs):
45
+ ssh = args[0]
46
+ ssh_host = ssh.ssh_host
47
+ args_with_out_self = args[1:]
48
+ request_data = dict(args = args_with_out_self,
49
+ kwargs = kwargs ,
50
+ ssh_host = ssh_host )
51
+ if self.add_caller_signature_to_cache_key:
52
+ frames = call_stack_frames_data(8) # todo: refactor this to separate method
53
+ caller_signature = (f"{frames[0].get('name')}:{frames[0].get('lineno')} | "
54
+ f"{frames[1].get('name')}:{frames[1].get('lineno')} | "
55
+ f"{frames[2].get('name')}:{frames[2].get('lineno')}'")
56
+ request_data['caller_signature'] = caller_signature # this adds support for different caches to the same method call (main limitation is that it is directly tied with the line numbers)
57
+
58
+ return dict_to_toml(request_data)
59
+
60
+ def requests_data__all(self):
61
+ requests_data__all = super().requests_data__all()
62
+ for item in requests_data__all:
63
+ request_data_raw = item.get('request_data')
64
+ request_data_original = toml_to_dict(json_loads(request_data_raw))
65
+ item['request_data'] = request_data_original
66
+ return requests_data__all