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.
- osbot_utils/base_classes/Kwargs_To_Self.py +3 -54
- osbot_utils/base_classes/Type_Safe.py +6 -0
- osbot_utils/context_managers/disable_root_loggers.py +30 -0
- osbot_utils/helpers/CFormat.py +147 -0
- osbot_utils/helpers/CPrint.py +5 -50
- osbot_utils/helpers/Print_Table.py +1 -1
- osbot_utils/helpers/cache_requests/Cache__Requests__Actions.py +23 -0
- osbot_utils/helpers/cache_requests/Cache__Requests__Config.py +32 -0
- osbot_utils/helpers/cache_requests/Cache__Requests__Data.py +105 -0
- osbot_utils/helpers/cache_requests/Cache__Requests__Invoke.py +55 -0
- osbot_utils/helpers/cache_requests/Cache__Requests__Row.py +64 -0
- osbot_utils/helpers/cache_requests/Cache__Requests__Table.py +16 -0
- osbot_utils/helpers/cache_requests/__init__.py +0 -0
- osbot_utils/helpers/cache_requests/flows/flow__Cache__Requests.py +11 -0
- osbot_utils/helpers/flows/Flow.py +145 -0
- osbot_utils/helpers/flows/Task.py +18 -0
- osbot_utils/helpers/flows/__init__.py +0 -0
- osbot_utils/helpers/sqlite/{domains/schemas → cache}/Schema__Table__Requests.py +6 -4
- osbot_utils/helpers/sqlite/cache/Sqlite__Cache__Requests.py +104 -0
- osbot_utils/helpers/sqlite/{domains → cache}/Sqlite__Cache__Requests__Patch.py +10 -8
- osbot_utils/helpers/sqlite/cache/Sqlite__Cache__Requests__Sqlite.py +18 -0
- osbot_utils/helpers/sqlite/cache/Sqlite__Cache__Requests__Table.py +48 -0
- osbot_utils/helpers/sqlite/{domains → cache}/Sqlite__DB__Requests.py +8 -7
- osbot_utils/helpers/sqlite/cache/TestCase__Sqlite__Cache__Requests.py +35 -0
- osbot_utils/helpers/sqlite/cache/__init__.py +0 -0
- osbot_utils/helpers/sqlite/domains/Sqlite__DB__Local.py +6 -2
- osbot_utils/helpers/{SCP.py → ssh/SCP.py} +23 -20
- osbot_utils/helpers/ssh/SSH.py +30 -0
- osbot_utils/helpers/ssh/SSH__Cache__Requests.py +66 -0
- osbot_utils/helpers/ssh/SSH__Execute.py +158 -0
- osbot_utils/helpers/ssh/SSH__Health_Check.py +49 -0
- osbot_utils/helpers/ssh/SSH__Linux.py +106 -0
- osbot_utils/helpers/ssh/SSH__Python.py +48 -0
- osbot_utils/helpers/ssh/TestCase__SSH.py +50 -0
- osbot_utils/helpers/ssh/__init__.py +0 -0
- osbot_utils/helpers/trace/Trace_Call__Print_Lines.py +1 -1
- osbot_utils/testing/Logging.py +15 -5
- osbot_utils/testing/Pytest.py +18 -0
- osbot_utils/utils/Env.py +27 -9
- osbot_utils/utils/Json.py +2 -9
- osbot_utils/utils/Misc.py +16 -18
- osbot_utils/utils/Objects.py +17 -7
- osbot_utils/utils/Python_Logger.py +54 -38
- osbot_utils/utils/Str.py +20 -3
- osbot_utils/utils/Toml.py +33 -0
- osbot_utils/version +1 -1
- {osbot_utils-1.17.0.dist-info → osbot_utils-1.20.0.dist-info}/METADATA +2 -2
- {osbot_utils-1.17.0.dist-info → osbot_utils-1.20.0.dist-info}/RECORD +50 -23
- osbot_utils/helpers/SSH.py +0 -172
- osbot_utils/helpers/sqlite/domains/Sqlite__Cache__Requests.py +0 -214
- {osbot_utils-1.17.0.dist-info → osbot_utils-1.20.0.dist-info}/LICENSE +0 -0
- {osbot_utils-1.17.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
|
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:
|
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,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
|
osbot_utils/helpers/CPrint.py
CHANGED
@@ -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
|
-
|
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
|
40
|
-
|
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
|
-
|
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.
|
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
|