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
@@ -6,10 +6,9 @@ import inspect
6
6
  import sys
7
7
  import types
8
8
  import typing
9
- from decimal import Decimal
9
+ from decimal import Decimal
10
10
  from enum import Enum, EnumMeta
11
11
  from typing import List
12
-
13
12
  from osbot_utils.base_classes.Type_Safe__List import Type_Safe__List
14
13
  from osbot_utils.utils.Dev import pprint
15
14
  from osbot_utils.utils.Json import json_parse
@@ -48,57 +47,7 @@ immutable_types = (bool, int, float, complex, str, tuple, frozenset, bytes, None
48
47
  # for example if we have an method like def add_node(self, title: str, call_index: int):
49
48
  # throw an exception if the type of the value passed in is not the same as the one defined in the method
50
49
 
51
- class Kwargs_To_Self: # todo: check if the description below is still relevant (since a lot has changed since it was created)
52
- """
53
- A mixin class to strictly assign keyword arguments to pre-defined instance attributes during initialization.
54
-
55
- This base class provides an __init__ method that assigns values from keyword
56
- arguments to instance attributes. If an attribute with the same name as a key
57
- from the kwargs is defined in the class, it will be set to the value from kwargs.
58
- If the key does not match any predefined attribute names, an exception is raised.
59
-
60
- This behavior enforces strict control over the attributes of instances, ensuring
61
- that only predefined attributes can be set at the time of instantiation and avoids
62
- silent attribute creation which can lead to bugs in the code.
63
-
64
- Usage:
65
- class MyConfigurableClass(Kwargs_To_Self):
66
- attribute1 = 'default_value'
67
- attribute2 = True
68
- attribute3 : str
69
- attribute4 : list
70
- attribute4 : int = 42
71
-
72
- # Other methods can be added here
73
-
74
- # Correctly override default values by passing keyword arguments
75
- instance = MyConfigurableClass(attribute1='new_value', attribute2=False)
76
-
77
- # This will raise an exception as 'attribute3' is not predefined
78
- # instance = MyConfigurableClass(attribute3='invalid_attribute')
79
-
80
- this will also assign the default value to any variable that has a type defined.
81
- In the example above the default values (mapped by __default__kwargs__ and __locals__) will be:
82
- attribute1 = 'default_value'
83
- attribute2 = True
84
- attribute3 = '' # default value of str
85
- attribute4 = [] # default value of list
86
- attribute4 = 42 # defined value in the class
87
-
88
- Note:
89
- It is important that all attributes which may be set at instantiation are
90
- predefined in the class. Failure to do so will result in an exception being
91
- raised.
92
-
93
- Methods:
94
- __init__(**kwargs): The initializer that handles the assignment of keyword
95
- arguments to instance attributes. It enforces strict
96
- attribute assignment rules, only allowing attributes
97
- that are already defined in the class to be set.
98
- """
99
-
100
- #__lock_attributes__ = False
101
- #__type_safety__ = True
50
+ class Kwargs_To_Self:
102
51
 
103
52
  def __init__(self, **kwargs):
104
53
  """
@@ -150,7 +99,7 @@ class Kwargs_To_Self: # todo: check if the description below is st
150
99
  # raise AttributeError(f"'[Object Locked] Current object is locked (with __lock_attributes__=True) which prevents new attributes allocations (i.e. setattr calls). In this case {type(self).__name__}' object has no attribute '{name}'") from None
151
100
 
152
101
  if value is not None:
153
- check_1 = value_type_matches_obj_annotation_for_attr(self, name, value)
102
+ check_1 = value_type_matches_obj_annotation_for_attr (self, name, value)
154
103
  check_2 = value_type_matches_obj_annotation_for_union_attr(self, name, value)
155
104
  if (check_1 is False and check_2 is None or
156
105
  check_1 is None and check_2 is False or
@@ -0,0 +1,6 @@
1
+ from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
2
+
3
+ # todo: refactor all of Kwargs_To_Self into this class (in a way to minimize the side effects, since
4
+ # Kwargs_To_Self is used in many places in the codebase)
5
+
6
+ Type_Safe = Kwargs_To_Self
@@ -0,0 +1,30 @@
1
+ import logging
2
+
3
+ from osbot_utils.utils.Dev import pprint
4
+ from osbot_utils.utils.Misc import timestamp_utc_now
5
+
6
+
7
+ class disable_root_loggers():
8
+ def __init__(self):
9
+ self.original_root_loggers = []
10
+
11
+
12
+ def __enter__(self):
13
+
14
+ self.capture_root_loggers() # capture current loggers
15
+ logging.root.handlers.clear() # removes all loggers
16
+ return self
17
+
18
+ def __exit__(self, exc_type, exc_val, exc_tb):
19
+ self.restore_root_loggers() # restores original loggers
20
+ return False # ensures that any exceptions that happened are rethrown
21
+
22
+ def capture_root_loggers(self):
23
+ for logger in logging.root.handlers:
24
+ self.original_root_loggers.append(logger)
25
+ return self
26
+
27
+ def restore_root_loggers(self):
28
+ for logger in self.original_root_loggers:
29
+ logging.root.handlers.append(logger)
30
+ return self
@@ -0,0 +1,147 @@
1
+ # note these attributes will be replaced by methods by CPrint. This is done like this in order to:
2
+ # - Have code complete on CPrint
3
+ # - not have the write the code for each of the methods
4
+ # - have a good and logical place to capture the ID of the color
5
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
6
+
7
+
8
+ class CFormat_Colors:
9
+ black = "30"
10
+ blue = "34"
11
+ cyan = "36"
12
+ grey = "38;5;15"
13
+ green = "32"
14
+ none = "0" # no color # note: this is using the ascii color reset code, see if there are any side effects
15
+ magenta = "35"
16
+ red = "31"
17
+ white = "38;5;15"
18
+ yellow = "33"
19
+ bright_black = "90"
20
+ bright_red = "91"
21
+ bright_green = "92"
22
+ bright_yellow = "93"
23
+ bright_blue = "94"
24
+ bright_magenta = "95"
25
+ bright_cyan = "96"
26
+ bright_white = "97"
27
+ dark_red = "38;5;124" # see https://github.com/fidian/ansi for a full list
28
+ dark_grey = "38;5;8"
29
+
30
+ bold = "1" # ANSI escape code for bold
31
+ italic = "3" # ANSI escape code for italic
32
+ underline = "4" # ANSI escape code for underline
33
+ blink = "5" # (no visual change in pycharm console) ANSI escape code for blink (not widely supported)
34
+ inverse = "7" # ANSI escape code for inverse/negative
35
+ strikethrough = "9" # ANSI escape code for strikethrough
36
+ double_underline = "21" # ANSI escape code for double underline
37
+ faint = "2" # (no visual change in pycharm console) ANSI escape code for faint/dim
38
+ framed = "51" # ANSI escape code for framed (rarely supported)
39
+ encircled = "52" # ANSI escape code for encircled (rarely supported)
40
+ overlined = "53" # (no visual change in pycharm console) ANSI escape code for overlined
41
+
42
+ class CFormat(CFormat_Colors, Type_Safe):
43
+ apply_colors: bool = True
44
+ auto_bold : bool = False
45
+
46
+ def __getattribute__(self, name): # this will replace the attributes defined in colors with methods that will call add_to_current_line with the params provided
47
+ if name != '__getattribute__' and hasattr(CFormat_Colors, name): # if name is one of the colors defined in Colors
48
+ def method(*args, **kwargs): # create a method to replace the attribute
49
+ return self.apply_color_to_text(name, *args, **kwargs) # pass the data to add_with_color
50
+ return method
51
+ return super().__getattribute__(name) # if the attribute name is not one of the attributes defined in colors, restore the normal behaviour of __getattribute__
52
+
53
+ def rgb(self, r, g, b, *args):
54
+ color_code = f"38;2;{r};{g};{b}"
55
+ return self.text_with_colors(color_code, *args)
56
+
57
+ def bg_rgb(self, r, g, b, *args):
58
+ color_code = f"48;2;{r};{g};{b}"
59
+ return self.text_with_colors(color_code, *args)
60
+
61
+ def cmyk(self, c, m, y, k, *args):
62
+ # CMYK to RGB conversion
63
+ r = 255 * (1 - c / 100) * (1 - k / 100)
64
+ g = 255 * (1 - m / 100) * (1 - k / 100)
65
+ b = 255 * (1 - y / 100) * (1 - k / 100)
66
+ color_code = f"38;2;{int(r)};{int(g)};{int(b)}"
67
+ return self.text_with_colors(color_code, *args)
68
+
69
+ def bg_cmyk(self, c, m, y, k, *args):
70
+ # CMYK to RGB conversion
71
+ r = 255 * (1 - c / 100) * (1 - k / 100)
72
+ g = 255 * (1 - m / 100) * (1 - k / 100)
73
+ b = 255 * (1 - y / 100) * (1 - k / 100)
74
+ color_code = f"48;2;{int(r)};{int(g)};{int(b)}"
75
+ return self.text_with_colors(color_code, *args)
76
+
77
+ def hex(self, hex_code, *args):
78
+ # Convert hex to RGB
79
+ hex_code = hex_code.lstrip('#')
80
+ r, g, b = int(hex_code[0:2], 16), int(hex_code[2:4], 16), int(hex_code[4:6], 16)
81
+ color_code = f"38;2;{r};{g};{b}"
82
+ return self.text_with_colors(color_code, *args)
83
+
84
+ def bg_hex(self, hex_code, *args):
85
+ # Convert hex to RGB
86
+ hex_code = hex_code.lstrip('#')
87
+ r, g, b = int(hex_code[0:2], 16), int(hex_code[2:4], 16), int(hex_code[4:6], 16)
88
+ color_code = f"48;2;{r};{g};{b}"
89
+ return self.text_with_colors(color_code, *args)
90
+
91
+ def apply_color_to_text(self, color_name, *args, **kwargs):
92
+ color_code = getattr(CFormat_Colors, color_name) # capture the color from the Colors class
93
+ return self.apply_color_code_to_text(color_code, *args, **kwargs)
94
+
95
+ def apply_color_code_to_text(self, color_code, *args, **kwargs):
96
+ return self.text_with_colors(color_code, *args, **kwargs)
97
+
98
+ def text_with_colors(self, color_code, *args, **kwargs):
99
+ args = [str(arg) for arg in args] # Convert all non-string arguments to strings
100
+ text = "".join(args)
101
+ if self.apply_colors:
102
+ color_start = f"\033[{color_code}m" # ANSI color code start and end
103
+ color_end = "\033[0m"
104
+ text_with_color = f"{color_start}{text}{color_end}"
105
+ if self.auto_bold:
106
+ return f"\033[1m{text_with_color}\033[0m"
107
+ return text_with_color
108
+ else:
109
+ return text
110
+
111
+ cformat = CFormat()
112
+
113
+ # ascii formating static helper methods
114
+ f_bold = cformat.bold
115
+ f_italic = cformat.italic
116
+ f_underline = cformat.underline
117
+ f_blink = cformat.blink
118
+ f_inverse = cformat.inverse
119
+ f_strikethrough = cformat.strikethrough
120
+ f_double_underline = cformat.double_underline
121
+ f_faint = cformat.faint
122
+ f_framed = cformat.framed
123
+ f_encircled = cformat.encircled
124
+ f_overlined = cformat.overlined
125
+
126
+
127
+ # ascii colors static helper methods
128
+ f_black = cformat.black
129
+ f_red = cformat.red
130
+ f_blue = cformat.blue
131
+ f_cyan = cformat.cyan
132
+ f_grey = cformat.grey
133
+ f_green = cformat.green
134
+ f_none = cformat.none
135
+ f_magenta = cformat.magenta
136
+ f_white = cformat.white
137
+ f_yellow = cformat.yellow
138
+ f_bright_black = cformat.bright_black
139
+ f_bright_red = cformat.bright_red
140
+ f_bright_green = cformat.bright_green
141
+ f_bright_yellow = cformat.bright_yellow
142
+ f_bright_blue = cformat.bright_blue
143
+ f_bright_magenta = cformat.bright_magenta
144
+ f_bright_cyan = cformat.bright_cyan
145
+ f_bright_white = cformat.bright_white
146
+ f_dark_red = cformat.dark_red
147
+ f_dark_grey = cformat.dark_grey
@@ -1,65 +1,20 @@
1
1
  from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
2
+ from osbot_utils.helpers.CFormat import CFormat, CFormat_Colors
2
3
 
3
4
 
4
- # note these attributes will be replaced by methods by CPrint. This is done like this in order to:
5
- # - Have code complete on CPrint
6
- # - not have the write the code for each of the methods
7
- # - have a good and logical place to capture the ID of the color
8
-
9
- class Colors:
10
- black = "30"
11
- blue = "34"
12
- cyan = "36"
13
- grey = "38;5;15"
14
- green = "32"
15
- none = "0"
16
- magenta = "35"
17
- red = "31"
18
- white = "38;5;15"
19
- yellow = "33"
20
- bright_black = "90"
21
- bright_red = "91"
22
- bright_green = "92"
23
- bright_yellow = "93"
24
- bright_blue = "94"
25
- bright_magenta = "95"
26
- bright_cyan = "96"
27
- bright_white = "97"
28
- dark_red = "38;5;124" # see https://github.com/fidian/ansi for a full list
29
-
30
-
31
- class CPrint(Colors, Kwargs_To_Self):
32
- apply_colors : bool = True
5
+ class CPrint(CFormat):
33
6
  auto_new_line : bool = True
34
7
  auto_print : bool = True
35
8
  clear_on_print : bool = True
36
9
  current_line : str
37
10
  lines : list
38
11
 
39
- def __getattribute__(self, name): # this will replace the attributes defined in colors with methods that will call add_to_current_line with the params provided
40
- if name != '__getattribute__' and hasattr(Colors, name): # if name is one of the colors defined in Colors
41
- def method(*args, **kwargs): # create a method to replace the attribute
42
- return self.add_with_color(name, *args, **kwargs) # pass the data to add_with_color
43
- return method
44
- return super().__getattribute__(name) # if the attribute name is not one of the attributes defined in colors, restore the normal behaviour of __getattribute__
45
-
46
- def add_with_color(self, color_name, *args, **kwargs):
47
- color_code = getattr(Colors, color_name) # capture the color from the Colors class
48
- self.add_to_current_line(color_code, *args, **kwargs) # add the color code to the current line
12
+ def apply_color_code_to_text(self, color_code, *args, **kwargs):
13
+ self.add_to_current_line(color_code, *args, **kwargs)
49
14
  return self
50
15
 
51
16
  def add_to_current_line(self, color_code, *args, **kwargs):
52
- args = [str(arg) for arg in args] # Convert all non-string arguments to strings
53
- text = "".join(args) # Concatenate all arguments without a space (to have beter support for multi-prints per line)
54
- if self.apply_colors:
55
- color_start = f"\033[{color_code}m" # ANSI color code start and end
56
- color_end = "\033[0m"
57
- kwargs['end'] = '' # remove the default print end (which is \n)
58
-
59
- text_with_colors = f"{color_start}{text}{color_end}"
60
- self.current_line += text_with_colors
61
- else:
62
- self.current_line += text
17
+ self.current_line += self.text_with_colors(color_code, *args, **kwargs)
63
18
  self.apply_config_options()
64
19
  return self
65
20
 
@@ -1,5 +1,5 @@
1
1
  from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
2
- from osbot_utils.utils.Misc import ansi_text_visible_length
2
+ from osbot_utils.utils.Str import ansi_text_visible_length
3
3
 
4
4
  raw_data = """|-------------------------------------------------------------------------------------|
5
5
  | BOTO3 REST calls (via BaseClient._make_api_call) |
@@ -0,0 +1,23 @@
1
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
2
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Row import Cache__Requests__Row
3
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Table import Cache__Requests__Table
4
+ from osbot_utils.utils.Json import json_dumps
5
+ from osbot_utils.utils.Misc import str_sha256
6
+
7
+
8
+ class Cache__Requests__Actions(Type_Safe):
9
+ cache_table : Cache__Requests__Table
10
+ cache_row : Cache__Requests__Row
11
+
12
+ def cache_add(self, request_data, response_data):
13
+ new_row_obj = self.cache_row.create_new_cache_obj(request_data, response_data)
14
+ return self.cache_table.row_add_and_commit(new_row_obj)
15
+
16
+ def cache_delete(self, request_data):
17
+ request_data = json_dumps(request_data)
18
+ request_data_sha256 = str_sha256(request_data)
19
+ return self.cache_table.rows_delete_where(request_hash=request_data_sha256)
20
+
21
+ def create_new_cache_row_data(self, request_data, response_data):
22
+ new_row_data = self.cache_row.create_new_cache_row_data(request_data, response_data)
23
+ return new_row_data
@@ -0,0 +1,32 @@
1
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
2
+
3
+
4
+ class Cache__Requests__Config(Type_Safe):
5
+ add_timestamp : bool = True
6
+ add_source_location :bool = True
7
+ enabled : bool = True
8
+ update_mode : bool = False
9
+ cache_only_mode : bool = False
10
+ pickle_response : bool = False
11
+ capture_exceptions : bool = False # once this is working, it might be more useful to have this set to true
12
+ exception_classes : list
13
+
14
+ def disable(self):
15
+ self.enabled = False
16
+ return self
17
+
18
+ def enable(self):
19
+ self.enabled = True
20
+ return self
21
+
22
+ def only_from_cache(self, value=True):
23
+ self.cache_only_mode = value
24
+ return self
25
+
26
+ def set__add_timestamp(self, value):
27
+ self.add_timestamp = value
28
+ return self
29
+
30
+ def update(self, value=True):
31
+ self.update_mode = value
32
+ return self
@@ -0,0 +1,105 @@
1
+ import types
2
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
3
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Config import Cache__Requests__Config
4
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Table import Cache__Requests__Table
5
+ from osbot_utils.utils.Json import json_dumps, json_loads
6
+ from osbot_utils.utils.Misc import str_sha256
7
+ from osbot_utils.utils.Objects import pickle_save_to_bytes, pickle_load_from_bytes
8
+
9
+
10
+ class Cache__Requests__Data(Type_Safe):
11
+ cache_table : Cache__Requests__Table
12
+ cache_request_data : types.MethodType
13
+ config : Cache__Requests__Config
14
+
15
+ def cache_entries(self):
16
+ return self.cache_table.rows()
17
+
18
+ def cache_entry(self, request_data):
19
+ request_data = json_dumps(request_data)
20
+ request_data_sha256 = str_sha256(request_data)
21
+ data = self.cache_table.select_rows_where(request_hash=request_data_sha256)
22
+
23
+ if len(data) > 0: # todo: add logic to handle (or log), where there are multiple entries with the same hash
24
+ return data[0]
25
+ return {}
26
+
27
+ def cache_entry_comments(self, *args, **target_kwargs):
28
+ cache_entry = self.cache_entry_for_request_params(*args, **target_kwargs)
29
+ return cache_entry.get('comments')
30
+
31
+ def cache_entry_comments_update(self, new_comments, *args, **target_kwargs):
32
+ cache_entry = self.cache_entry_for_request_params(*args, **target_kwargs)
33
+ request_hash = cache_entry.get('request_hash')
34
+ update_fields = dict(comments=new_comments)
35
+ query_conditions = dict(request_hash=request_hash)
36
+ result = self.cache_table.row_update(update_fields, query_conditions)
37
+ return result
38
+
39
+ def cache_entry_for_request_params(self, *args, **target_kwargs):
40
+ request_data = self.cache_request_data(*args, **target_kwargs)
41
+ return self.cache_entry(request_data)
42
+
43
+
44
+
45
+ def response_data_for__request_hash(self, request_hash):
46
+ rows = self.cache_table.rows_where__request_hash(request_hash)
47
+ if len(rows) > 0:
48
+ cache_entry = rows[0]
49
+ response_data_obj = self.response_data_deserialize(cache_entry)
50
+ return response_data_obj
51
+ return {}
52
+
53
+ def requests_data__all(self):
54
+ requests_data = []
55
+ for row in self.cache_table.rows():
56
+ req_id = row.get('id')
57
+ request_data = row.get('request_data')
58
+ request_hash = row.get('request_hash')
59
+ request_comments = row.get('comments')
60
+
61
+ request_data_obj = dict(request_data = request_data ,
62
+ _id = req_id ,
63
+ _hash = request_hash ,
64
+ _comments = request_comments)
65
+
66
+ requests_data.append(request_data_obj)
67
+ return requests_data
68
+
69
+ def response_data__all(self):
70
+ responses_data = []
71
+ for row in self.cache_table.rows():
72
+ response_data_obj = self.convert_row__to__response_data_obj(row)
73
+ responses_data.append(response_data_obj)
74
+ return responses_data
75
+
76
+ def convert_row__to__response_data_obj(self, row):
77
+ row_id = row.get('id' )
78
+ comments = row.get('comments' )
79
+ request_hash = row.get('request_hash' )
80
+ response_hash = row.get('response_hash')
81
+ response_data = self.response_data_deserialize(row)
82
+ response_data_obj = dict( comments = comments ,
83
+ row_id = row_id ,
84
+ request_hash = request_hash ,
85
+ response_hash = response_hash ,
86
+ response_data = response_data )
87
+ return response_data_obj
88
+
89
+ def response_data_deserialize(self, cache_entry):
90
+ if self.config.pickle_response: # todo: refactor our this logic, since this needs to be done in sync with the response_type value
91
+ response_bytes = cache_entry.get('response_bytes')
92
+ response_data_obj = pickle_load_from_bytes(response_bytes)
93
+ else:
94
+ response_data = cache_entry.get('response_data')
95
+ response_data_obj = json_loads(response_data) # todo: review the other scenarios of response_type
96
+ if self.config.capture_exceptions:
97
+ if (type(response_data_obj) is Exception or # raise if it is an exception
98
+ type(response_data_obj) in self.config.exception_classes): # or if one of the types that have been set as being exception classes
99
+ raise response_data_obj
100
+ return response_data_obj
101
+
102
+ def response_data_serialize(self, response_data):
103
+ if self.config.pickle_response:
104
+ return pickle_save_to_bytes(response_data)
105
+ return response_data
@@ -0,0 +1,55 @@
1
+ import types
2
+
3
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
4
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Actions import Cache__Requests__Actions
5
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Config import Cache__Requests__Config
6
+ from osbot_utils.helpers.cache_requests.Cache__Requests__Data import Cache__Requests__Data
7
+
8
+
9
+ class Cache__Requests__Invoke(Type_Safe):
10
+ cache_actions : Cache__Requests__Actions
11
+ cache_data : Cache__Requests__Data
12
+ config : Cache__Requests__Config
13
+ on_invoke_target : types.FunctionType
14
+
15
+ def invoke(self, target, target_args, target_kwargs):
16
+ return self.invoke_with_cache(target, target_args, target_kwargs)
17
+
18
+ def invoke_target(self, target, target_args, target_kwargs):
19
+ if self.on_invoke_target:
20
+ raw_response = self.on_invoke_target(target, target_args, target_kwargs)
21
+ else:
22
+ raw_response = target(*target_args, **target_kwargs)
23
+ return self.transform_raw_response(raw_response)
24
+
25
+ def invoke_with_cache(self, target, target_args, target_kwargs, request_data=None):
26
+ if self.config.enabled is False:
27
+ if self.config.cache_only_mode:
28
+ return None
29
+ return self.invoke_target(target, target_args, target_kwargs)
30
+ if request_data is None:
31
+ request_data = self.cache_data.cache_request_data(*target_args, **target_kwargs)
32
+ cache_entry = self.cache_data.cache_entry(request_data)
33
+ if cache_entry:
34
+ if self.config.update_mode is True:
35
+ self.cache_actions.cache_delete(request_data)
36
+ else:
37
+ return self.cache_data.response_data_deserialize(cache_entry)
38
+ if self.config.cache_only_mode is False:
39
+ return self.invoke_target__and_add_to_cache(request_data, target, target_args, target_kwargs)
40
+
41
+
42
+ def invoke_target__and_add_to_cache(self,request_data, target, target_args, target_kwargs):
43
+ try:
44
+ response_data_obj = self.invoke_target(target, target_args, target_kwargs)
45
+ response_data = self.cache_data.response_data_serialize(response_data_obj)
46
+ self.cache_actions.cache_add(request_data=request_data, response_data=response_data)
47
+ return response_data_obj
48
+ except Exception as exception:
49
+ if self.config.capture_exceptions:
50
+ response_data = self.cache_data.response_data_serialize(exception)
51
+ self.cache_actions.cache_add(request_data=request_data, response_data=response_data)
52
+ raise exception
53
+
54
+ def transform_raw_response(self, raw_response):
55
+ return raw_response
@@ -0,0 +1,64 @@
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.cache_requests.Cache__Requests__Table import Cache__Requests__Table
4
+ from osbot_utils.utils.Json import json_dumps
5
+ from osbot_utils.utils.Misc import str_sha256, timestamp_utc_now, bytes_sha256
6
+
7
+
8
+
9
+ class Cache__Requests__Row(Type_Safe):
10
+ config : Cache__Requests__Config
11
+ cache_table : Cache__Requests__Table
12
+
13
+ # todo: duplicated method with Sqlite__Cache__Requests (which is the one that is being overwritten)
14
+ # this need to change to use the 'cache_key' workflow
15
+ def cache_request_data(self, *args, **target_kwargs):
16
+ return {'args' : list(args) ,
17
+ 'kwargs': target_kwargs } # convert the args tuple to a list since that is what it will be once it is serialised
18
+
19
+ def create_new_cache_obj(self, request_data, response_data):
20
+ new_row_data = self.create_new_cache_row_data(request_data, response_data)
21
+ new_row_obj = self.cache_table.new_row_obj(new_row_data)
22
+ return new_row_obj
23
+
24
+ def create_new_cache_row_data(self, request_data, response_data): # todo refactor this method into sub methods (one that map the request and one that maps the response)
25
+ request_data_json = json_dumps(request_data)
26
+ request_data_hash = str_sha256(request_data_json)
27
+ if self.config.add_timestamp:
28
+ timestamp = timestamp_utc_now()
29
+ else:
30
+ timestamp = 0
31
+ cache_cata = dict(request_data = request_data_json ,
32
+ request_hash = request_data_hash ,
33
+ timestamp = timestamp )
34
+
35
+ self.map_response_data(cache_cata, response_data)
36
+ return cache_cata
37
+
38
+ def map_response_data(self, cache_cata, response_data):
39
+ response_data_str = ''
40
+ response_data_bytes = b''
41
+ if self.config.pickle_response:
42
+ response_type = 'pickle'
43
+ response_data_bytes = response_data
44
+ response_data_hash = bytes_sha256(response_data_bytes)
45
+
46
+ else:
47
+ if type(response_data) is bytes:
48
+ response_type = 'bytes'
49
+ response_data_bytes = response_data
50
+ response_data_hash = bytes_sha256(response_data_bytes)
51
+ elif type(response_data) is dict:
52
+ response_type = 'dict'
53
+ response_data_str = json_dumps(response_data)
54
+ response_data_hash = str_sha256(response_data_str)
55
+ else:
56
+ response_type = 'str'
57
+ response_data_str = str(response_data)
58
+ response_data_hash = str_sha256(response_data_str)
59
+
60
+ cache_cata['response_bytes'] = response_data_bytes
61
+ cache_cata['response_data' ] = response_data_str
62
+ cache_cata['response_hash' ] = response_data_hash
63
+ cache_cata['response_type' ] = response_type
64
+
@@ -0,0 +1,16 @@
1
+ from osbot_utils.base_classes.Type_Safe import Type_Safe
2
+
3
+
4
+ class Cache__Requests__Table(Type_Safe):
5
+
6
+ def _table_create(self):
7
+ raise NotImplementedError
8
+
9
+ def clear(self):
10
+ raise NotImplementedError
11
+
12
+ def row_add_and_commit(self, row_obj=None):
13
+ raise NotImplementedError
14
+
15
+ def rows_delete_where(self, **query_conditions):
16
+ raise NotImplementedError
File without changes
@@ -0,0 +1,11 @@
1
+ import functools
2
+
3
+ from osbot_utils.helpers.flows.Flow import flow
4
+
5
+
6
+ class flow__Cache_Requests:
7
+
8
+ @flow()
9
+ def invoke_function(self, function, *args, **kwargs):
10
+ print('in invoke function')
11
+ return function(*args, **kwargs)