osbot-utils 1.17.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 +27 -9
  40. osbot_utils/utils/Json.py +2 -9
  41. osbot_utils/utils/Misc.py +16 -18
  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.17.0.dist-info → osbot_utils-1.20.0.dist-info}/METADATA +2 -2
  48. {osbot_utils-1.17.0.dist-info → osbot_utils-1.20.0.dist-info}/RECORD +50 -23
  49. osbot_utils/helpers/SSH.py +0 -172
  50. osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests.py +0 -214
  51. {osbot_utils-1.17.0.dist-info → osbot_utils-1.20.0.dist-info}/LICENSE +0 -0
  52. {osbot_utils-1.17.0.dist-info → osbot_utils-1.20.0.dist-info}/WHEEL +0 -0
@@ -0,0 +1,158 @@
1
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
2
+ from osbot_utils.context_managers.capture_duration import capture_duration
3
+ from osbot_utils.decorators.lists.group_by import group_by
4
+ from osbot_utils.decorators.lists.index_by import index_by
5
+ from osbot_utils.utils.Dev import pprint
6
+ from osbot_utils.utils.Env import get_env
7
+ from osbot_utils.utils.Misc import str_to_int, str_to_bool
8
+ from osbot_utils.utils.Process import start_process, run_process
9
+ from osbot_utils.utils.Status import status_error
10
+
11
+ ENV_VAR__SSH__HOST = 'SSH__HOST'
12
+ ENV_VAR__SSH__PORT = 'SSH__PORT'
13
+ ENV_VAR__SSH__KEY_FILE = 'SSH__KEY_FILE'
14
+ ENV_VAR__SSH__USER = 'SSH__USER'
15
+ ENV_VAR__SSH__STRICT_HOST_CHECK = 'SSH__STRICT_HOST_CHECK'
16
+
17
+
18
+ class SSH__Execute(Type_Safe):
19
+ ssh_host : str
20
+ ssh_port : int = 22
21
+ ssh_key_file : str
22
+ ssh_key_user : str
23
+ strict_host_check : bool = False
24
+
25
+ # execution & other commands # todo refactor into separate class
26
+ def exec(self, command):
27
+ return self.execute_command__return_stdout(command)
28
+
29
+ def exec__print(self, command):
30
+ result = self.execute_command__return_stdout(command)
31
+ self.print_header_for_command(command)
32
+ print(result)
33
+ return result
34
+
35
+ def execute_command(self, command):
36
+ if self.ssh_setup_ok() and command:
37
+ ssh_args = self.execute_command_args(command)
38
+ with capture_duration() as duration:
39
+ result = start_process("ssh", ssh_args) # execute command using subprocess.run(...)
40
+ result['duration'] = duration.data()
41
+ return result
42
+ return status_error(error='in execute_command not all required vars were setup')
43
+
44
+ def execute_command__print(self, command):
45
+ self.print_header_for_command(command)
46
+ result = self.execute_command(command)
47
+ pprint(result)
48
+ return result
49
+
50
+ def execute_ssh_args(self):
51
+ ssh_args = []
52
+ if self.ssh_port:
53
+ ssh_args += ['-p', str(self.ssh_port)]
54
+ if self.strict_host_check is False:
55
+ ssh_args += ['-o', 'StrictHostKeyChecking=no']
56
+ if self.ssh_key_file:
57
+ ssh_args += ['-i', self.ssh_key_file]
58
+ return ssh_args
59
+
60
+ def execute_command_args(self, command=None):
61
+ ssh_args = self.execute_ssh_args()
62
+ if self.ssh_host:
63
+ ssh_args += [self.execute_command_target_host()]
64
+ if command:
65
+ ssh_args += [command]
66
+ return ssh_args
67
+
68
+ def execute_command_target_host(self):
69
+ if self.ssh_key_user:
70
+ return f'{self.ssh_key_user}@{self.ssh_host}'
71
+ else:
72
+ return f'{self.ssh_host}'
73
+
74
+ def execute_command__return_stdout(self, command):
75
+ return self.execute_command(command).get('stdout', '').strip()
76
+
77
+ def execute_command__return_stderr(self, command):
78
+ return self.execute_command(command).get('stderr', '').strip()
79
+
80
+ @index_by
81
+ @group_by
82
+ def execute_command__return_dict(self, command):
83
+ stdout = self.execute_command(command).get('stdout').strip()
84
+ return self.parse_stdout_to_dict(stdout)
85
+
86
+ @index_by
87
+ @group_by
88
+ def execute_command__return_list(self, command):
89
+ stdout = self.execute_command(command).get('stdout').strip()
90
+ return self.parse_stdout_to_list(stdout)
91
+
92
+ # setup commands # todo refactor into separate class
93
+ def setup(self):
94
+ self.setup_using_env_vars()
95
+ return self
96
+
97
+ def setup_using_env_vars(self): # move this to a CONFIG class (see code in SSH__Health_Check)
98
+ ssh_host = get_env(ENV_VAR__SSH__HOST )
99
+ ssh_port = get_env(ENV_VAR__SSH__PORT )
100
+ ssh_key_file = get_env(ENV_VAR__SSH__KEY_FILE )
101
+ ssh_key_user = get_env(ENV_VAR__SSH__USER )
102
+ ssh_strict_host_check = get_env(ENV_VAR__SSH__STRICT_HOST_CHECK )
103
+ if ssh_host:
104
+ self.ssh_host = ssh_host
105
+ if ssh_port:
106
+ self.ssh_port = str_to_int(ssh_port)
107
+ if ssh_key_file:
108
+ self.ssh_key_file = ssh_key_file
109
+ if ssh_key_user:
110
+ self.ssh_key_user = ssh_key_user
111
+ if ssh_strict_host_check is not None:
112
+ self.strict_host_check = str_to_bool(ssh_strict_host_check)
113
+
114
+ def parse_stdout_to_dict(self, stdout):
115
+ lines = stdout.splitlines()
116
+ headers = lines[0].split()
117
+ result = []
118
+
119
+ for line in lines[1:]: # Split each line into parts based on whitespace
120
+ parts = line.split() # Combine the parts with headers to create a dictionary
121
+ entry = {headers[i]: parts[i] for i in range(len(headers))}
122
+ result.append(entry)
123
+
124
+ return result
125
+
126
+ def parse_stdout_to_list(self, stdout): # todo: add support for more ways to split the data
127
+ lines = stdout.splitlines()
128
+ return lines
129
+
130
+
131
+ def ssh_setup_ok(self):
132
+ # todo: add check to see if ssh executable exists (this check can be cached)
133
+ if self.ssh_host and self.ssh_key_file and self.ssh_key_user:
134
+ return True
135
+ return False
136
+
137
+ def ssh_not__setup_ok(self):
138
+ return self.ssh_setup_ok() is False
139
+
140
+ # print helpers
141
+ # def print_ls(self, path=''):
142
+ # pprint(self.ls(path))
143
+ # return self
144
+
145
+ def print_exec(self, command=''):
146
+ return self.exec__print(command)
147
+
148
+ def print_header_for_command(self, command):
149
+ print('\n')
150
+ print('*' * (30 + len(command)))
151
+ print(f'****** stdout for: {command} ******')
152
+ print('*' * (30 + len(command)))
153
+ print()
154
+
155
+ def remove_server_ssh_host_fingerprint(self): # todo: refactor to utils class
156
+ cmd_ssh_keyscan = "ssh-keygen"
157
+ cmd_remove_host = ['-R', f'[{self.ssh_host}]:{self.ssh_port}']
158
+ return run_process(cmd_ssh_keyscan, cmd_remove_host)
@@ -0,0 +1,49 @@
1
+ from osbot_utils.helpers.ssh.SSH__Execute import ENV_VAR__SSH__HOST, ENV_VAR__SSH__KEY_FILE, ENV_VAR__SSH__USER, \
2
+ ENV_VAR__SSH__PORT, ENV_VAR__SSH__STRICT_HOST_CHECK, SSH__Execute
3
+ from osbot_utils.utils.Env import get_env
4
+ from osbot_utils.utils.Misc import list_set
5
+ from osbot_utils.utils.Status import status_ok, status_error
6
+
7
+ ENV_VARS__FOR_SSH = {'ssh_host' : ENV_VAR__SSH__HOST ,
8
+ 'ssh_key_file' : ENV_VAR__SSH__KEY_FILE ,
9
+ 'ssh_key_user' : ENV_VAR__SSH__USER ,
10
+ 'ssh_port' : ENV_VAR__SSH__PORT ,
11
+ 'strict_host_check': ENV_VAR__SSH__STRICT_HOST_CHECK }
12
+
13
+ class SSH__Health_Check(SSH__Execute):
14
+
15
+ def check_connection(self):
16
+ text_message = 'test connection' #random_text('echo')
17
+ response = self.execute_command(f'echo {text_message}')
18
+ if response.get('status') == 'ok':
19
+ stderr = response.get('stderr').strip()
20
+ if stderr == '':
21
+ stdout = response.get('stdout').strip()
22
+ if stdout == text_message:
23
+ return status_ok(message='connection ok')
24
+ else:
25
+ return status_error(message=f'expected stdout did not march: {text_message} != {stdout}')
26
+ else:
27
+ return status_error(message=f'stderr was not empty', error=stderr, data=response)
28
+ else:
29
+ return status_error(message=f'request failed', data=response)
30
+
31
+ def env_vars_names(self):
32
+ return list_set(ENV_VARS__FOR_SSH)
33
+
34
+ def env_vars_values(self):
35
+ values = {}
36
+ for key, value in ENV_VARS__FOR_SSH.items():
37
+ env_value = get_env(value)
38
+ values[key] = env_value
39
+ return values
40
+
41
+ def env_vars_set_ok(self):
42
+ env_values = self.env_vars_values()
43
+ if (env_values.get('ssh_host' ) and
44
+ env_values.get('ssh_key_file') and
45
+ env_values.get('ssh_key_user') ):
46
+ return True
47
+ return False
48
+
49
+
@@ -0,0 +1,106 @@
1
+ from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
2
+ from osbot_utils.decorators.lists.index_by import index_by
3
+ from osbot_utils.helpers.ssh.SSH__Execute import SSH__Execute
4
+
5
+
6
+ class SSH__Linux(Kwargs_To_Self):
7
+ ssh_execute : SSH__Execute
8
+
9
+ def apt_update(self):
10
+ return self.ssh_execute.execute_command__return_stdout('apt-get update')
11
+
12
+ def apt_install(self, package_name):
13
+ return self.ssh_execute.execute_command(f'apt-get install -y {package_name}')
14
+
15
+ def cat(self, path=''):
16
+ command = f'cat {path}'
17
+ return self.ssh_execute.execute_command__return_stdout(command)
18
+
19
+ @index_by
20
+ def disk_space(self):
21
+ command = "df -h"
22
+ stdout = self.ssh_execute.execute_command__return_stdout(command)
23
+ stdout_disk_space = stdout.replace('Mounted on', 'Mounted_on') # todo, find a better way to do this
24
+ disk_space = self.ssh_execute.parse_stdout_to_dict(stdout_disk_space)
25
+ return disk_space
26
+
27
+ def dir_exists(self, folder_name):
28
+ message__folder_exists = "Folder exists"
29
+ message__folder_not_exists = "Folder does not exist"
30
+ test_command = f'test -d {folder_name} && echo "{message__folder_exists}" || echo "{message__folder_not_exists}"'
31
+ result = self.ssh_execute.execute_command__return_stdout(test_command)
32
+ if result == message__folder_exists:
33
+ return True
34
+ if result == message__folder_not_exists:
35
+ return False
36
+
37
+ def echo(self, message):
38
+ return self.ssh_execute.execute_command__return_stdout(f"echo '{message}'")
39
+
40
+ def find(self, path=''):
41
+ command = f'find {path}'
42
+ return self.ssh_execute.execute_command__return_list(command)
43
+
44
+ def ls(self, path=''):
45
+ command = f'ls {path}'
46
+ ls_raw = self.ssh_execute.execute_command__return_stdout(command)
47
+ return ls_raw.splitlines()
48
+
49
+ def memory_usage(self):
50
+ command = "free -h"
51
+ memory_usage_raw = self.ssh_execute.execute_command__return_stdout(command) # todo: add fix for data parsing issue
52
+ return memory_usage_raw.splitlines()
53
+
54
+
55
+ def mkdir(self, folder):
56
+ command = f'mkdir -p {folder}'
57
+ return self.ssh_execute.execute_command(command)
58
+
59
+ def mv(self, source, destination):
60
+ command = f'mv {source} {destination}'
61
+ return self.ssh_execute.execute_command(command)
62
+
63
+ def pwd(self):
64
+ return self.ssh_execute.execute_command__return_stdout('pwd')
65
+
66
+ def rm(self, path=''):
67
+ command = f'rm {path}'
68
+ return self.ssh_execute.execute_command__return_stderr(command)
69
+
70
+ def rmdir(self, folder):
71
+ command = f'rmdir {folder}'
72
+ return self.ssh_execute.execute_command(command)
73
+
74
+ def running_processes(self,**kwargs):
75
+ command = "ps aux"
76
+ return self.ssh_execute.execute_command__return_dict(command, **kwargs)
77
+
78
+ def system_uptime(self):
79
+ command = "uptime"
80
+ uptime_raw = self.ssh_execute.execute_command__return_stdout(command)
81
+ return uptime_raw.strip()
82
+
83
+ def uname(self):
84
+ return self.ssh_execute.execute_command__return_stdout('uname')
85
+
86
+ def which(self, target):
87
+ command = f'which {target}' # todo: security-vuln: add protection against code injection
88
+ return self.ssh_execute.execute_command__return_stdout(command)
89
+
90
+ def whoami(self):
91
+ command = f'whoami'
92
+ return self.ssh_execute.execute_command__return_stdout(command)
93
+
94
+ # todo: add methods below (and respective tests)
95
+
96
+ # def ifconfig(self):
97
+ # command = "export PATH=$PATH:/sbin && ifconfig" # todo add example with PATH modification
98
+ # return self.execute_command__return_stdout(command)
99
+
100
+ # def ifconfig(self): # todo add command to execute in separate bash (see when it is needed)
101
+ # command = "bash -l -c 'ifconfig'"
102
+ # return self.execute_command__return_stdout(command)
103
+ # if port_forward: # todo: add support for port forward (this will need async execution)
104
+ # local_port = port_forward.get('local_port' )
105
+ # remote_ip = port_forward.get('remote_ip' )
106
+ # remote_port = port_forward.get('remote_port')
@@ -0,0 +1,48 @@
1
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
2
+ from osbot_utils.helpers.ssh.SSH__Execute import SSH__Execute
3
+ from osbot_utils.helpers.ssh.SSH__Linux import SSH__Linux
4
+ from osbot_utils.utils.Dev import pprint
5
+ from osbot_utils.utils.Functions import function_source_code
6
+ from osbot_utils.utils.Lists import list_index_by
7
+
8
+ PYTHON3__LINUX__INSTALLER = 'python3'
9
+
10
+ class SSH__Python(Type_Safe):
11
+ ssh_execute: SSH__Execute
12
+ ssh_linux : SSH__Linux
13
+
14
+ def execute_python__code(self, python_code, python_executable='python3'):
15
+ python_command = f"{python_executable} -c \"{python_code}\""
16
+ return self.ssh_execute.execute_command(python_command)
17
+
18
+ def execute_python__code__return_stdout(self, *args, **kwargs):
19
+ return self.execute_python__code(*args, **kwargs).get('stdout').strip()
20
+
21
+ def execute_python__function(self, function, python_executable='python3'):
22
+ function_name = function.__name__
23
+ function_code = function_source_code(function)
24
+ exec_code = f"{function_code}\nresult= {function_name}(); print(result)"
25
+ return self.execute_python__code(exec_code)
26
+
27
+ def execute_python__function__return_stderr(self, *args, **kwargs):
28
+ return self.execute_python__function(*args, **kwargs).get('stderr').strip()
29
+
30
+ def execute_python__function__return_stdout(self, *args, **kwargs):
31
+ return self.execute_python__function(*args, **kwargs).get('stdout').strip()
32
+
33
+ def install_python3(self):
34
+ return self.ssh_linux.apt_install(PYTHON3__LINUX__INSTALLER)
35
+
36
+ def pip_list(self):
37
+ pip_list = self.ssh_execute.execute_command__return_dict('pip list')
38
+ return list_index_by(pip_list, 'Package')
39
+
40
+ def pip_install(self, package_name):
41
+ return self.ssh_execute.execute_command__return_stdout(f'pip install {package_name}')
42
+
43
+ def pip_version(self):
44
+ return self.ssh_execute.execute_command__return_stdout('pip --version')
45
+
46
+ def python_version(self):
47
+ return self.ssh_execute.execute_command__return_stdout('python3 --version')
48
+
@@ -0,0 +1,50 @@
1
+ from unittest import TestCase
2
+
3
+ import osbot_utils
4
+ from osbot_utils.helpers.ssh.SSH import SSH
5
+ from osbot_utils.helpers.ssh.SSH__Cache__Requests import SSH__Cache__Requests
6
+ from osbot_utils.helpers.ssh.SSH__Execute import SSH__Execute
7
+ from osbot_utils.utils.Env import load_dotenv
8
+ from osbot_utils.utils.Files import path_combine
9
+
10
+ ENV_FILE__WITH_ENV_VARS = "../.ssh.env"
11
+
12
+ class TestCase__SSH(TestCase):
13
+ ssh : SSH
14
+ cache: SSH__Cache__Requests
15
+
16
+ @classmethod
17
+ def setUpClass(cls):
18
+ cls.load_dotenv()
19
+ cls.ssh = SSH().setup()
20
+ if not cls.ssh.ssh_execute().ssh_host:
21
+ import pytest # we can only import this locally since this dependency doesn't exist in the main osbot_utils codebase
22
+ pytest.skip("SSH host not set")
23
+
24
+ cls.cache = SSH__Cache__Requests()
25
+ cls.cache.patch_apply()
26
+
27
+ @classmethod
28
+ def tearDownClass(cls):
29
+ cls.cache.patch_restore()
30
+ assert SSH__Execute.execute_command.__qualname__ == 'SSH__Execute.execute_command'
31
+
32
+ @staticmethod
33
+ def load_dotenv():
34
+ env_file_path = path_combine(osbot_utils.path, ENV_FILE__WITH_ENV_VARS)
35
+ load_dotenv(dotenv_path=env_file_path)
36
+
37
+ def cache_disable(self):
38
+ self.cache.disable()
39
+
40
+ def cache_update(self):
41
+ self.cache.update()
42
+
43
+ def ssh_execute(self):
44
+ return self.ssh.ssh_execute()
45
+
46
+ def ssh_linux(self):
47
+ return self.ssh.ssh_linux()
48
+
49
+ def ssh_python(self):
50
+ return self.ssh.ssh_python()
File without changes
@@ -1,8 +1,8 @@
1
1
  from osbot_utils.utils.Lists import list_sorted
2
2
  from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
3
- from osbot_utils.utils.Misc import ansi_text_visible_length
4
3
  from osbot_utils.helpers.trace.Trace_Call__Config import Trace_Call__Config
5
4
  from osbot_utils.helpers.trace.Trace_Call__Print_Traces import text_grey, text_bold_green, text_olive, text_light_grey
5
+ from osbot_utils.utils.Str import ansi_text_visible_length
6
6
 
7
7
 
8
8
  class Trace_Call__Print_Lines(Kwargs_To_Self):
@@ -8,8 +8,9 @@ from osbot_utils.decorators.methods.cache_on_self import cache_on_self
8
8
 
9
9
  #DEFAULT_LOG_FORMAT = '%(asctime)s.%(msecs)03d %(levelname)s - %(message)s'
10
10
  DEFAULT_LOG_FORMAT = '%(levelname)s - %(message)s'
11
- DEFAULT_LOG_LEVEL = logging.DEBUG
11
+ DEFAULT_LOG_LEVEL = logging.INFO
12
12
  DEFAULT_DATE_FORMAT = '%M:%S'
13
+
13
14
  class Logging:
14
15
 
15
16
  def __init__(self, target=None, log_level: int = None, log_format=None, log_to_console=False, date_format=None):
@@ -27,7 +28,7 @@ class Logging:
27
28
  stream_handler = logging.StreamHandler(stream=stream)
28
29
  self.logger().addHandler(stream_handler)
29
30
  self.set_logger_level()
30
- self.set_format(stream_handler)
31
+ self.set_format_on_stream_handler(stream_handler)
31
32
 
32
33
  return stream_handler
33
34
 
@@ -38,7 +39,7 @@ class Logging:
38
39
  self.target = self.target.__name__
39
40
  return logging.getLogger(self.target)
40
41
 
41
- def enable_log_to_console(self, log_level=logging.INFO):
42
+ def enable_log_to_console(self, log_level=None):
42
43
  self.log_to_sys_stdout()
43
44
  self.set_logger_level(log_level)
44
45
  return self
@@ -60,7 +61,7 @@ class Logging:
60
61
  return self.add_stream_handler(log_stream)
61
62
 
62
63
 
63
- def set_format(self, stream_handler):
64
+ def set_format_on_stream_handler(self, stream_handler):
64
65
  formatter = logging.Formatter(fmt=self.log_format, datefmt=self.date_format)
65
66
  stream_handler.setFormatter(formatter)
66
67
  return formatter
@@ -71,7 +72,16 @@ class Logging:
71
72
  return self
72
73
 
73
74
  def set_logger_level(self, level=None):
74
- self.logger().setLevel(level or self.log_level)
75
+ if level:
76
+ self.log_level = level
77
+ self.logger().setLevel(self.log_level)
78
+
79
+ def set_log_format(self, log_format=None, date_format=None):
80
+ if log_format:
81
+ self.log_format = log_format
82
+ if date_format:
83
+ self.date_format = date_format
84
+ return self
75
85
 
76
86
 
77
87
  def info (self,message, *args, **kwargs): self.logger().info (message, *args, **kwargs)
@@ -0,0 +1,18 @@
1
+ from osbot_utils.utils.Env import get_env, load_dotenv, in_python_debugger
2
+
3
+ needs_load_dotenv = True
4
+
5
+ def skip_pytest(message=r"Skipping pytest for some reason ¯\_o_/¯"):
6
+ import pytest # we can only import this locally since this dependency doesn't exist in the main osbot_utils codebase
7
+ pytest.skip(message)
8
+
9
+ def skip_pytest__if_env_var_is_not_set(env_var_name):
10
+ if needs_load_dotenv:
11
+ load_dotenv()
12
+
13
+ if not get_env(env_var_name):
14
+ skip_pytest(f"Skipping tests because the {env_var_name} env var doesn't have a value")
15
+
16
+ def skip__if_in_python_debugger():
17
+ if in_python_debugger():
18
+ skip_pytest("Skipping tests because we are in a debugger")
osbot_utils/utils/Env.py CHANGED
@@ -1,11 +1,10 @@
1
- # In Misc.py
2
- import os
3
- from sys import platform
4
1
 
5
- from osbot_utils.utils.Dev import pprint
6
- from osbot_utils.utils.Files import all_parent_folders
7
- from osbot_utils.utils.Misc import list_set
8
- from osbot_utils.utils.Str import strip_quotes
2
+ import os
3
+ import sys
4
+ from sys import platform
5
+ from osbot_utils.utils.Files import all_parent_folders, file_exists
6
+ from osbot_utils.utils.Misc import list_set
7
+ from osbot_utils.utils.Str import strip_quotes
9
8
 
10
9
  def env__home_root():
11
10
  return os.getenv('HOME') == '/root'
@@ -39,7 +38,7 @@ def env_vars(reload_vars=False):
39
38
  return data
40
39
 
41
40
  def env_load_from_file(path, override=False):
42
- if os.path.exists(path):
41
+ if file_exists(path):
43
42
  with open(path) as f:
44
43
  for line in f:
45
44
  line = line.strip()
@@ -64,6 +63,20 @@ def env_unload_from_file(path):
64
63
  if key in os.environ: # Remove the environment variable if it exists
65
64
  del os.environ[key]
66
65
 
66
+ def in_github_action():
67
+ return os.getenv('GITHUB_ACTIONS') == 'true'
68
+
69
+ def in_python_debugger():
70
+ if sys.gettrace() is not None: # Check for a trace function
71
+ return True
72
+
73
+ pycharm_hosted = os.getenv('PYCHARM_HOSTED') == '1' # Check for PyCharm specific environment variables and other potential indicators
74
+ pydevd_load_values_async = os.getenv('PYDEVD_LOAD_VALUES_ASYNC') is not None
75
+ if pycharm_hosted and pydevd_load_values_async:
76
+ return True
77
+
78
+ return False
79
+
67
80
  def load_dotenv(dotenv_path=None, override=False):
68
81
  if dotenv_path: # If a specific dotenv path is provided, load from it
69
82
  env_load_from_file(dotenv_path, override)
@@ -76,6 +89,10 @@ def load_dotenv(dotenv_path=None, override=False):
76
89
  break # Stop after loading the first .env file # Stop after loading the first .env file
77
90
 
78
91
 
92
+ def not_in_github_action():
93
+ return in_github_action() is False
94
+
95
+
79
96
  def unload_dotenv(dotenv_path=None):
80
97
  if dotenv_path: # If a specific dotenv path is provided, unload from it
81
98
  env_unload_from_file(dotenv_path)
@@ -88,4 +105,5 @@ def unload_dotenv(dotenv_path=None):
88
105
  break # Stop after unloading the first .env file
89
106
 
90
107
 
91
- env_load = load_dotenv
108
+ env_load = load_dotenv
109
+ get_env = os.getenv
osbot_utils/utils/Json.py CHANGED
@@ -1,16 +1,9 @@
1
1
  import json
2
- import gzip
3
- import logging
4
2
  import os
5
3
 
6
4
  from osbot_utils.utils.Misc import str_lines, str_md5, str_sha256
7
- from osbot_utils.utils.Status import log_exception
8
-
9
- logger_json = logging.getLogger() # todo: start using this API for capturing error messages from methods bellow
10
-
11
5
  from osbot_utils.utils.Files import file_create_gz, file_create, load_file_gz, file_contents, file_lines, file_lines_gz
12
6
 
13
-
14
7
  def json_dumps(python_object, indent=4, pretty=True, sort_keys=False, default=str, raise_exception=False):
15
8
  if python_object:
16
9
  try:
@@ -19,7 +12,7 @@ def json_dumps(python_object, indent=4, pretty=True, sort_keys=False, default=st
19
12
  return json.dumps(python_object, default=default)
20
13
  except Exception as error:
21
14
  error_message = f'Error in load_json: {error}'
22
- log_exception(message=error_message, error=error)
15
+ #log_exception(message=error_message, error=error) # todo: find a better way to do this , since this never worked well
23
16
  if raise_exception:
24
17
  raise error
25
18
 
@@ -89,7 +82,7 @@ class Json:
89
82
  try:
90
83
  return json.loads(json_data)
91
84
  except Exception as error:
92
- log_exception(message='Error in load_json', error=error)
85
+ #log_exception(message='Error in load_json', error=error)
93
86
  if raise_exception:
94
87
  raise error
95
88