osbot-utils 2.62.0__py3-none-any.whl → 2.64.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/decorators/methods/cache_on_self.py +35 -64
- osbot_utils/helpers/Obj_Id.py +2 -1
- osbot_utils/helpers/Safe_Id.py +2 -2
- osbot_utils/helpers/cache_on_self/Cache_Controller.py +28 -0
- osbot_utils/helpers/cache_on_self/Cache_Key_Generator.py +113 -0
- osbot_utils/helpers/cache_on_self/Cache_Metrics.py +31 -0
- osbot_utils/helpers/cache_on_self/Cache_On_Self.py +124 -0
- osbot_utils/helpers/cache_on_self/Cache_Storage.py +33 -0
- osbot_utils/helpers/cache_on_self/__init__.py +145 -0
- osbot_utils/type_safe/Type_Safe__List.py +4 -2
- osbot_utils/type_safe/Type_Safe__Method.py +1 -1
- osbot_utils/type_safe/Type_Safe__Primitive.py +39 -3
- osbot_utils/type_safe/shared/Type_Safe__Raise_Exception.py +3 -3
- osbot_utils/type_safe/shared/Type_Safe__Validation.py +1 -1
- osbot_utils/type_safe/steps/Type_Safe__Step__Init.py +1 -1
- osbot_utils/version +1 -1
- {osbot_utils-2.62.0.dist-info → osbot_utils-2.64.0.dist-info}/METADATA +2 -2
- {osbot_utils-2.62.0.dist-info → osbot_utils-2.64.0.dist-info}/RECORD +20 -14
- {osbot_utils-2.62.0.dist-info → osbot_utils-2.64.0.dist-info}/LICENSE +0 -0
- {osbot_utils-2.62.0.dist-info → osbot_utils-2.64.0.dist-info}/WHEEL +0 -0
@@ -1,80 +1,51 @@
|
|
1
|
-
|
1
|
+
import inspect
|
2
|
+
from functools import wraps
|
3
|
+
from typing import Any, Callable, TypeVar, Dict
|
4
|
+
from weakref import WeakKeyDictionary
|
2
5
|
|
3
|
-
|
4
|
-
CACHE_ON_SELF_TYPES = [int, float, bytearray, bytes, bool,
|
5
|
-
complex, str]
|
6
|
+
from osbot_utils.helpers.cache_on_self.Cache_On_Self import Cache_On_Self
|
6
7
|
|
7
|
-
# not supported for now (need to understand side effect, )
|
8
|
-
# - set, dict, range,, tuple, list : cloud have inner objects
|
9
|
-
# - memoryview : returns unique memory location value
|
10
8
|
|
9
|
+
T = TypeVar('T', bound=Callable[..., Any])
|
11
10
|
|
11
|
+
# Global registry of cache managers per instance per method
|
12
|
+
# Structure: {instance: {method_name: Cache_On_Self}}
|
13
|
+
_cache_managers_registry: WeakKeyDictionary[Any, Dict[str, Cache_On_Self]] = WeakKeyDictionary()
|
12
14
|
|
13
15
|
|
14
|
-
T = TypeVar('T', bound=Callable[..., Any]) # so that we have type hinting when using this class
|
15
16
|
|
16
|
-
def cache_on_self(function: T) -> T:
|
17
|
-
|
18
|
-
|
17
|
+
def cache_on_self(function: T) -> T:
|
18
|
+
"""
|
19
|
+
Decorator to cache method results on the instance.
|
20
|
+
|
21
|
+
Use this for cases where we want the cache to be tied to the
|
22
|
+
Class instance (i.e. not global for all executions)
|
23
|
+
"""
|
24
|
+
function_name = function.__name__
|
19
25
|
|
20
26
|
@wraps(function)
|
21
27
|
def wrapper(*args, **kwargs):
|
28
|
+
# Extract self from args
|
29
|
+
if not args:
|
30
|
+
raise ValueError("cache_on_self could not find self - no arguments provided")
|
22
31
|
|
32
|
+
self = args[0]
|
23
33
|
|
24
|
-
|
25
|
-
|
34
|
+
# Get or create cache manager for this instance/method combination
|
35
|
+
if self not in _cache_managers_registry:
|
36
|
+
_cache_managers_registry[self] = {}
|
26
37
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
reload_cache = True # set reload value to True
|
31
|
-
del kwargs['reload_cache'] # remove the reload parameter from the kwargs
|
38
|
+
if function_name not in _cache_managers_registry[self]:
|
39
|
+
# Create new cache manager for this instance/method
|
40
|
+
_cache_managers_registry[self][function_name] = Cache_On_Self(function=function)
|
32
41
|
|
33
|
-
|
34
|
-
cache_id = cache_on_self__get_cache_in_key(function, args, kwargs) # get cache_id value
|
42
|
+
cache_manager = _cache_managers_registry[self][function_name]
|
35
43
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
return getattr(self, cache_id) # return the return value
|
40
|
-
return wrapper
|
44
|
+
# Handle special __return__ parameter
|
45
|
+
if kwargs.get('__return__') == 'cache_on_self':
|
46
|
+
return cache_manager
|
41
47
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
if type(arg) in CACHE_ON_SELF_TYPES:
|
47
|
-
args_values_as_str += str(arg)
|
48
|
-
return args_values_as_str
|
49
|
-
|
50
|
-
def cache_on_self__kwargs_to_str(kwargs):
|
51
|
-
kwargs_values_as_str = ''
|
52
|
-
if kwargs:
|
53
|
-
for key,value in kwargs.items():
|
54
|
-
if type(value) in CACHE_ON_SELF_TYPES:
|
55
|
-
kwargs_values_as_str += f'{key}:{value}|'
|
56
|
-
return kwargs_values_as_str
|
57
|
-
|
58
|
-
def cache_on_self__get_cache_in_key(function, args=None, kwargs=None):
|
59
|
-
from osbot_utils.utils.Misc import str_md5
|
60
|
-
|
61
|
-
key_name = function.__name__
|
62
|
-
args_md5 = ''
|
63
|
-
kwargs_md5 = ''
|
64
|
-
args_values_as_str = cache_on_self__args_to_str(args)
|
65
|
-
kwargs_values_as_str = cache_on_self__kwargs_to_str(kwargs)
|
66
|
-
if args_values_as_str:
|
67
|
-
args_md5 = str_md5(args_values_as_str)
|
68
|
-
if kwargs_values_as_str:
|
69
|
-
kwargs_md5 = str_md5(kwargs_values_as_str)
|
70
|
-
return f'{CACHE_ON_SELF_KEY_PREFIX}_{key_name}_{args_md5}_{kwargs_md5}'
|
71
|
-
|
72
|
-
# class_name = self_obj.__class__.__name__
|
73
|
-
#
|
74
|
-
# function_name = function_obj.__name__
|
75
|
-
# if params:
|
76
|
-
# params_as_string = '_'.join(str(x) for x in params).replace('/',' ')
|
77
|
-
# params_md5 = str_md5(params_as_string)
|
78
|
-
# return f'{class_name}_{function_name}_{params_md5}.gz'
|
79
|
-
# else:
|
80
|
-
# return f'{class_name}_{function_name}.gz'
|
48
|
+
# Normal call
|
49
|
+
return cache_manager.handle_call(args, kwargs)
|
50
|
+
|
51
|
+
return wrapper
|
osbot_utils/helpers/Obj_Id.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
import random
|
2
|
+
from osbot_utils.type_safe.Type_Safe__Primitive import Type_Safe__Primitive
|
2
3
|
|
3
4
|
_hex_table = [f"{i:02x}" for i in range(256)]
|
4
5
|
|
@@ -14,7 +15,7 @@ def is_obj_id(value: str):
|
|
14
15
|
def new_obj_id():
|
15
16
|
return hex(random.getrandbits(32))[2:].zfill(8) # slice off '0x' and pad
|
16
17
|
|
17
|
-
class Obj_Id(str):
|
18
|
+
class Obj_Id(Type_Safe__Primitive,str):
|
18
19
|
def __new__(cls, value: str=None):
|
19
20
|
if value:
|
20
21
|
if is_obj_id(value):
|
osbot_utils/helpers/Safe_Id.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from osbot_utils.type_safe.Type_Safe__Primitive import Type_Safe__Primitive
|
2
|
-
from osbot_utils.utils.Misc
|
3
|
-
from osbot_utils.utils.Str
|
2
|
+
from osbot_utils.utils.Misc import random_id_short
|
3
|
+
from osbot_utils.utils.Str import safe_id
|
4
4
|
|
5
5
|
SAFE_ID__MAX_LENGTH = 512
|
6
6
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import inspect
|
2
|
+
from typing import Any, Dict
|
3
|
+
|
4
|
+
|
5
|
+
class Cache_Controller: # Controls cache behavior and reload logic
|
6
|
+
|
7
|
+
def extract_clean_kwargs(self, kwargs: Dict[str, Any]) -> Dict[str, Any]: # Remove special parameters from kwargs
|
8
|
+
return {k: v for k, v in kwargs.items()
|
9
|
+
if k not in ['reload_cache', '__return__']}
|
10
|
+
|
11
|
+
def should_reload(self, kwargs : Dict[str, Any] ,
|
12
|
+
reload_next_flag: bool ) -> bool: # Determine if cache should be reloaded
|
13
|
+
if reload_next_flag:
|
14
|
+
return True
|
15
|
+
return kwargs.get('reload_cache', False) is True
|
16
|
+
|
17
|
+
def should_return_cache_manager(self, kwargs: Dict[str, Any]) -> bool: # Check if should return cache manager
|
18
|
+
return kwargs.get('__return__') == 'cache_on_self'
|
19
|
+
|
20
|
+
def extract_self_from_args(self, args: tuple) -> Any: # Validate and extract self from args
|
21
|
+
if len(args) == 0:
|
22
|
+
raise ValueError("cache_on_self could not find self - no arguments provided")
|
23
|
+
|
24
|
+
potential_self = args[0]
|
25
|
+
if not inspect.isclass(type(potential_self)):
|
26
|
+
raise ValueError("cache_on_self could not find self - first argument is not an instance")
|
27
|
+
|
28
|
+
return potential_self
|
@@ -0,0 +1,113 @@
|
|
1
|
+
import json
|
2
|
+
from typing import Callable, Dict, List, Tuple, Any, Set, FrozenSet
|
3
|
+
from osbot_utils.utils.Misc import str_md5
|
4
|
+
|
5
|
+
|
6
|
+
CACHE_ON_SELF_TYPES = [int, float, bytearray, bytes, bool, complex, str]
|
7
|
+
CACHE_ON_SELF_KEY_PREFIX = '__cache_on_self__'
|
8
|
+
|
9
|
+
|
10
|
+
class Cache_Key_Generator: # Handles all cache key generation logic
|
11
|
+
|
12
|
+
def __init__(self, supported_types: List[type] = None):
|
13
|
+
self.supported_types = supported_types or CACHE_ON_SELF_TYPES
|
14
|
+
|
15
|
+
def generate_key(self, function : Callable ,
|
16
|
+
args : Tuple [Any, ...],
|
17
|
+
kwargs : Dict [str, Any]
|
18
|
+
) -> str: # Generate cache key from function name and arguments
|
19
|
+
key_name = function.__name__
|
20
|
+
args_hash = self.get_args_hash(args)
|
21
|
+
kwargs_hash = self.get_kwargs_hash(kwargs)
|
22
|
+
return f'{CACHE_ON_SELF_KEY_PREFIX}_{key_name}_{args_hash}_{kwargs_hash}'
|
23
|
+
|
24
|
+
def get_args_hash(self, args: Tuple[Any, ...]) -> str: # Get hash for args or empty string if no hashable args
|
25
|
+
args_str = self.args_to_str(args)
|
26
|
+
if args_str:
|
27
|
+
return self.compute_hash(args_str)
|
28
|
+
return ''
|
29
|
+
|
30
|
+
def get_kwargs_hash(self, kwargs: Dict[str, Any]) -> str: # Get hash for kwargs or empty string if no hashable kwargs
|
31
|
+
kwargs_str = self.kwargs_to_str(kwargs)
|
32
|
+
if kwargs_str:
|
33
|
+
return self.compute_hash(kwargs_str)
|
34
|
+
return ''
|
35
|
+
|
36
|
+
def args_to_str(self, args: Tuple[Any, ...]) -> str: # Convert supported args to string representation
|
37
|
+
if not args:
|
38
|
+
return ''
|
39
|
+
|
40
|
+
parts = []
|
41
|
+
for i, arg in enumerate(args):
|
42
|
+
arg_str = self.value_to_cache_str(arg)
|
43
|
+
if arg_str:
|
44
|
+
# Include index to avoid collisions like (1, 23) vs (12, 3)
|
45
|
+
parts.append(f"[{i}]:{arg_str}")
|
46
|
+
return '|'.join(parts)
|
47
|
+
|
48
|
+
def kwargs_to_str(self, kwargs: Dict[str, Any]) -> str: # Convert supported kwargs to string representation
|
49
|
+
if not kwargs:
|
50
|
+
return ''
|
51
|
+
|
52
|
+
parts = []
|
53
|
+
for key, value in sorted(kwargs.items()): # Sort for consistent ordering
|
54
|
+
value_str = self.value_to_cache_str(value)
|
55
|
+
if value_str:
|
56
|
+
parts.append(f'{key}:{value_str}')
|
57
|
+
return '|'.join(parts)
|
58
|
+
|
59
|
+
def value_to_cache_str(self, value: Any) -> str: # Convert a value to a cacheable string representation
|
60
|
+
"""Convert any value to a string suitable for cache key generation.
|
61
|
+
|
62
|
+
Returns a type-prefixed string for all supported types.
|
63
|
+
Returns empty string ONLY for unsupported types.
|
64
|
+
"""
|
65
|
+
if value is None:
|
66
|
+
return '<none>'
|
67
|
+
|
68
|
+
# Handle primitive types with type prefix to avoid any collision
|
69
|
+
value_type = type(value)
|
70
|
+
if value_type in self.supported_types:
|
71
|
+
type_name = value_type.__name__
|
72
|
+
return f"<{type_name}>:{value}"
|
73
|
+
|
74
|
+
# Handle mutable types by converting to a stable string representation
|
75
|
+
try:
|
76
|
+
if isinstance(value, dict):
|
77
|
+
# Sort dict by keys for consistent ordering
|
78
|
+
sorted_items = sorted(value.items())
|
79
|
+
return f"<dict>:{json.dumps(sorted_items, sort_keys=True, separators=(',', ':'))}"
|
80
|
+
|
81
|
+
elif isinstance(value, list):
|
82
|
+
return f"<list>:{json.dumps(value, separators=(',', ':'))}"
|
83
|
+
|
84
|
+
elif isinstance(value, tuple):
|
85
|
+
return f"<tuple>:{json.dumps(value, separators=(',', ':'))}"
|
86
|
+
|
87
|
+
elif isinstance(value, set):
|
88
|
+
# Convert to sorted list for consistent ordering
|
89
|
+
return f"<set>:{json.dumps(sorted(list(value)), separators=(',', ':'))}"
|
90
|
+
|
91
|
+
elif isinstance(value, frozenset):
|
92
|
+
return f"<frozenset>:{json.dumps(sorted(list(value)), separators=(',', ':'))}"
|
93
|
+
|
94
|
+
else:
|
95
|
+
# For other types, try to use repr if it's likely to be stable
|
96
|
+
# This is a fallback - specific types should be handled above
|
97
|
+
if hasattr(value, '__dict__'):
|
98
|
+
# For objects, we could hash their dict representation
|
99
|
+
# But this is risky, so we'll skip for now
|
100
|
+
return ''
|
101
|
+
else:
|
102
|
+
# For other immutable types, try repr
|
103
|
+
try:
|
104
|
+
return f"other:{repr(value)}"
|
105
|
+
except:
|
106
|
+
return ''
|
107
|
+
|
108
|
+
except (TypeError, ValueError, RecursionError):
|
109
|
+
# If we can't serialize the value, skip it
|
110
|
+
return ''
|
111
|
+
|
112
|
+
def compute_hash(self, value: str) -> str: # Compute hash of string value
|
113
|
+
return str_md5(value)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class Cache_Metrics: # Track cache performance metrics
|
2
|
+
|
3
|
+
def __init__(self):
|
4
|
+
self.hits = 0
|
5
|
+
self.misses = 0
|
6
|
+
self.reloads = 0
|
7
|
+
self.key_generation_time = 0.0
|
8
|
+
self.cache_lookup_time = 0.0
|
9
|
+
self.function_execution_time = 0.0
|
10
|
+
|
11
|
+
@property
|
12
|
+
def hit_rate(self) -> float: # Calculate cache hit rate
|
13
|
+
total = self.hits + self.misses
|
14
|
+
return self.hits / total if total > 0 else 0.0
|
15
|
+
|
16
|
+
def record_hit(self) -> None: # Record cache hit
|
17
|
+
self.hits += 1
|
18
|
+
|
19
|
+
def record_miss(self) -> None: # Record cache miss
|
20
|
+
self.misses += 1
|
21
|
+
|
22
|
+
def record_reload(self) -> None: # Record cache reload
|
23
|
+
self.reloads += 1
|
24
|
+
|
25
|
+
def reset(self) -> None: # Reset all metrics
|
26
|
+
self.hits = 0
|
27
|
+
self.misses = 0
|
28
|
+
self.reloads = 0
|
29
|
+
self.key_generation_time = 0.0
|
30
|
+
self.cache_lookup_time = 0.0
|
31
|
+
self.function_execution_time = 0.0
|
@@ -0,0 +1,124 @@
|
|
1
|
+
from typing import Any, Callable, Dict, List
|
2
|
+
from osbot_utils.type_safe.Type_Safe import Type_Safe
|
3
|
+
from osbot_utils.helpers.cache_on_self.Cache_Controller import Cache_Controller
|
4
|
+
from osbot_utils.helpers.cache_on_self.Cache_Key_Generator import Cache_Key_Generator, CACHE_ON_SELF_KEY_PREFIX
|
5
|
+
from osbot_utils.helpers.cache_on_self.Cache_Metrics import Cache_Metrics
|
6
|
+
from osbot_utils.helpers.cache_on_self.Cache_Storage import Cache_Storage
|
7
|
+
|
8
|
+
|
9
|
+
class Cache_On_Self(Type_Safe):
|
10
|
+
cache_storage : Cache_Storage # Storage handler instance
|
11
|
+
controller : Cache_Controller # Controller instance
|
12
|
+
key_generator : Cache_Key_Generator # Key generator instance
|
13
|
+
metrics : Cache_Metrics # Metrics tracking
|
14
|
+
function : Callable # The wrapped function
|
15
|
+
function_name : str # Cached function name
|
16
|
+
no_args_key : str # Pre-computed key for no args
|
17
|
+
target_self : Any = None # The instance being cached on
|
18
|
+
current_cache_key : str = '' # Current cache key
|
19
|
+
current_cache_value : Any = None # Current cached value
|
20
|
+
reload_next : bool = False # Force reload on next call
|
21
|
+
disabled : bool = False # cache disable status
|
22
|
+
|
23
|
+
def __init__(self, function : Callable = None ,
|
24
|
+
supported_types: List[type] = None ):
|
25
|
+
super().__init__()
|
26
|
+
self.function = function
|
27
|
+
self.function_name = function.__name__ if function else ''
|
28
|
+
self.no_args_key = f'{CACHE_ON_SELF_KEY_PREFIX}_{self.function_name}__' # Pre-compute for performance
|
29
|
+
self.cache_storage = Cache_Storage()
|
30
|
+
self.controller = Cache_Controller()
|
31
|
+
self.key_generator = Cache_Key_Generator(supported_types)
|
32
|
+
self.metrics = Cache_Metrics()
|
33
|
+
|
34
|
+
def handle_call(self, args: tuple, kwargs: dict) -> Any: # Main entry point for cached calls
|
35
|
+
# Check if caching is disabled
|
36
|
+
if self.disabled:
|
37
|
+
return self.execute(args, kwargs)
|
38
|
+
|
39
|
+
if not kwargs and len(args) == 1: # Fast path for common case: no kwargs, single arg (self)
|
40
|
+
target_self = args[0]
|
41
|
+
cache_key = self.no_args_key
|
42
|
+
|
43
|
+
if self.cache_storage.has_cached_value(target_self, cache_key): # Use cache_storage
|
44
|
+
self.metrics.hits += 1 # Increment cache Hit
|
45
|
+
return self.cache_storage.get_cached_value(target_self, cache_key) # Use cache_storage
|
46
|
+
|
47
|
+
self.metrics.misses += 1 # Increment cache miss - execute and store (Direct increment)
|
48
|
+
result = self.function(*args)
|
49
|
+
self.cache_storage.set_cached_value(target_self, cache_key, result) # Use cache_storage instead of setattr
|
50
|
+
return result
|
51
|
+
|
52
|
+
return self.handle_call_full(args, kwargs)
|
53
|
+
|
54
|
+
def handle_call_full(self, args : tuple,
|
55
|
+
kwargs: dict
|
56
|
+
) -> Any: # Full logic for complex cases
|
57
|
+
# Check if caching is disabled
|
58
|
+
if self.disabled:
|
59
|
+
clean_kwargs = self.controller.extract_clean_kwargs(kwargs)
|
60
|
+
return self.execute(args, clean_kwargs)
|
61
|
+
|
62
|
+
# Extract values - don't store as instance variables to avoid recursion issues
|
63
|
+
target_self = self.controller.extract_self_from_args(args)
|
64
|
+
clean_kwargs = self.controller.extract_clean_kwargs(kwargs)
|
65
|
+
should_reload = self.controller.should_reload(kwargs, self.reload_next)
|
66
|
+
cache_key = self.key_generator.generate_key(self.function, args, clean_kwargs)
|
67
|
+
|
68
|
+
if should_reload:
|
69
|
+
self.reload_next = False # Reset reload flag
|
70
|
+
|
71
|
+
cached_exists = self.cache_storage.has_cached_value(target_self, cache_key)
|
72
|
+
|
73
|
+
if should_reload or not cached_exists:
|
74
|
+
# Execute and cache, passing the cache key directly
|
75
|
+
result = self.execute_and_cache(args, clean_kwargs, target_self, cache_key, should_reload)
|
76
|
+
else:
|
77
|
+
self.metrics.record_hit()
|
78
|
+
result = self.cache_storage.get_cached_value(target_self, cache_key)
|
79
|
+
|
80
|
+
# Update instance state only for external inspection
|
81
|
+
self.target_self = target_self
|
82
|
+
self.current_cache_key = cache_key
|
83
|
+
self.current_cache_value = result
|
84
|
+
|
85
|
+
return result
|
86
|
+
|
87
|
+
def execute(self, args : tuple,
|
88
|
+
kwargs : dict ,
|
89
|
+
) -> Any: # Execute function
|
90
|
+
return self.function(*args, **kwargs)
|
91
|
+
|
92
|
+
def execute_and_cache(self, args : tuple,
|
93
|
+
clean_kwargs : dict,
|
94
|
+
target_self : Any,
|
95
|
+
cache_key : str,
|
96
|
+
should_reload: bool) -> Any: # Execute function and store result
|
97
|
+
if should_reload:
|
98
|
+
self.metrics.record_reload()
|
99
|
+
else:
|
100
|
+
self.metrics.record_miss()
|
101
|
+
|
102
|
+
result = self.execute(args=args, kwargs=clean_kwargs)
|
103
|
+
self.cache_storage.set_cached_value(target_self, cache_key, result)
|
104
|
+
return result
|
105
|
+
|
106
|
+
def clear(self) -> None: # Clear current cache entry
|
107
|
+
if self.target_self and self.current_cache_key:
|
108
|
+
self.cache_storage.clear_key(self.target_self, self.current_cache_key)
|
109
|
+
|
110
|
+
def clear_all(self) -> None: # Clear all cache for current instance
|
111
|
+
if self.target_self:
|
112
|
+
self.cache_storage.clear_all(self.target_self)
|
113
|
+
|
114
|
+
def get_all_keys(self) -> List[str]: # Get all cache keys for current instance
|
115
|
+
if self.target_self:
|
116
|
+
return self.cache_storage.get_all_cache_keys(self.target_self)
|
117
|
+
return []
|
118
|
+
|
119
|
+
def stats(self) -> Dict[str, Any]: # Get cache statistics
|
120
|
+
return { 'hits' : self.metrics.hits ,
|
121
|
+
'misses' : self.metrics.misses ,
|
122
|
+
'reloads' : self.metrics.reloads ,
|
123
|
+
'hit_rate' : self.metrics.hit_rate ,
|
124
|
+
'cache_key' : self.current_cache_key }
|
@@ -0,0 +1,33 @@
|
|
1
|
+
from weakref import WeakKeyDictionary
|
2
|
+
from typing import Any, Dict, List
|
3
|
+
|
4
|
+
class Cache_Storage: # Handles all cache storage without polluting instance attributes
|
5
|
+
|
6
|
+
def __init__(self):
|
7
|
+
self.cache_data: WeakKeyDictionary = WeakKeyDictionary() # Use WeakKeyDictionary so cache is automatically cleaned up when instances are garbage collected , instance -> {cache_key: value}
|
8
|
+
|
9
|
+
def has_cached_value(self, instance: Any, cache_key: str) -> bool:
|
10
|
+
if instance in self.cache_data:
|
11
|
+
return cache_key in self.cache_data[instance]
|
12
|
+
return False
|
13
|
+
|
14
|
+
def get_cached_value(self, instance: Any, cache_key: str) -> Any:
|
15
|
+
return self.cache_data[instance][cache_key]
|
16
|
+
|
17
|
+
def set_cached_value(self, instance: Any, cache_key: str, value: Any) -> None:
|
18
|
+
if instance not in self.cache_data:
|
19
|
+
self.cache_data[instance] = {}
|
20
|
+
self.cache_data[instance][cache_key] = value
|
21
|
+
|
22
|
+
def get_all_cache_keys(self, instance: Any) -> List[str]:
|
23
|
+
if instance in self.cache_data:
|
24
|
+
return list(self.cache_data[instance].keys())
|
25
|
+
return []
|
26
|
+
|
27
|
+
def clear_key(self, instance: Any, cache_key: str) -> None:
|
28
|
+
if instance in self.cache_data:
|
29
|
+
self.cache_data[instance].pop(cache_key, None)
|
30
|
+
|
31
|
+
def clear_all(self, instance: Any) -> None:
|
32
|
+
if instance in self.cache_data:
|
33
|
+
del self.cache_data[instance]
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# note 1: this __init__.py file needs to be here (vs inside the /utils/ast/nodes folder)
|
2
|
+
# so that the methods registration happens as soon as somebody uses a method from the /utils/ast folder/package
|
3
|
+
# note 2: this is needed due to the circular dependencyes between the Ast_Node and the
|
4
|
+
# clases that use it as a base class
|
5
|
+
|
6
|
+
import ast
|
7
|
+
from osbot_utils.helpers.Type_Registry import type_registry
|
8
|
+
from osbot_utils.helpers.ast.nodes.Ast_Add import Ast_Add
|
9
|
+
from osbot_utils.helpers.ast.nodes.Ast_Alias import Ast_Alias
|
10
|
+
from osbot_utils.helpers.ast.nodes.Ast_And import Ast_And
|
11
|
+
from osbot_utils.helpers.ast.nodes.Ast_Argument import Ast_Argument
|
12
|
+
from osbot_utils.helpers.ast.nodes.Ast_Arguments import Ast_Arguments
|
13
|
+
from osbot_utils.helpers.ast.nodes.Ast_Assert import Ast_Assert
|
14
|
+
from osbot_utils.helpers.ast.nodes.Ast_Assign import Ast_Assign
|
15
|
+
from osbot_utils.helpers.ast.nodes.Ast_Attribute import Ast_Attribute
|
16
|
+
from osbot_utils.helpers.ast.nodes.Ast_Aug_Assign import Ast_Aug_Assign
|
17
|
+
from osbot_utils.helpers.ast.nodes.Ast_Bin_Op import Ast_Bin_Op
|
18
|
+
from osbot_utils.helpers.ast.nodes.Ast_Bool_Op import Ast_Bool_Op
|
19
|
+
from osbot_utils.helpers.ast.nodes.Ast_Break import Ast_Break
|
20
|
+
from osbot_utils.helpers.ast.nodes.Ast_Call import Ast_Call
|
21
|
+
from osbot_utils.helpers.ast.nodes.Ast_Class_Def import Ast_Class_Def
|
22
|
+
from osbot_utils.helpers.ast.nodes.Ast_Compare import Ast_Compare
|
23
|
+
from osbot_utils.helpers.ast.nodes.Ast_Comprehension import Ast_Comprehension
|
24
|
+
from osbot_utils.helpers.ast.nodes.Ast_Constant import Ast_Constant
|
25
|
+
from osbot_utils.helpers.ast.nodes.Ast_Continue import Ast_Continue
|
26
|
+
from osbot_utils.helpers.ast.nodes.Ast_Dict import Ast_Dict
|
27
|
+
from osbot_utils.helpers.ast.nodes.Ast_Eq import Ast_Eq
|
28
|
+
from osbot_utils.helpers.ast.nodes.Ast_Except_Handler import Ast_Except_Handler
|
29
|
+
from osbot_utils.helpers.ast.nodes.Ast_Expr import Ast_Expr
|
30
|
+
from osbot_utils.helpers.ast.nodes.Ast_For import Ast_For
|
31
|
+
from osbot_utils.helpers.ast.nodes.Ast_Function_Def import Ast_Function_Def
|
32
|
+
from osbot_utils.helpers.ast.nodes.Ast_Generator_Exp import Ast_Generator_Exp
|
33
|
+
from osbot_utils.helpers.ast.nodes.Ast_Gt import Ast_Gt
|
34
|
+
from osbot_utils.helpers.ast.nodes.Ast_GtE import Ast_GtE
|
35
|
+
from osbot_utils.helpers.ast.nodes.Ast_If import Ast_If
|
36
|
+
from osbot_utils.helpers.ast.nodes.Ast_If_Exp import Ast_If_Exp
|
37
|
+
from osbot_utils.helpers.ast.nodes.Ast_Import import Ast_Import
|
38
|
+
from osbot_utils.helpers.ast.nodes.Ast_Import_From import Ast_Import_From
|
39
|
+
from osbot_utils.helpers.ast.nodes.Ast_In import Ast_In
|
40
|
+
from osbot_utils.helpers.ast.nodes.Ast_Is import Ast_Is
|
41
|
+
from osbot_utils.helpers.ast.nodes.Ast_Is_Not import Ast_Is_Not
|
42
|
+
from osbot_utils.helpers.ast.nodes.Ast_Keyword import Ast_Keyword
|
43
|
+
from osbot_utils.helpers.ast.nodes.Ast_Lambda import Ast_Lambda
|
44
|
+
from osbot_utils.helpers.ast.nodes.Ast_List import Ast_List
|
45
|
+
from osbot_utils.helpers.ast.nodes.Ast_List_Comp import Ast_List_Comp
|
46
|
+
from osbot_utils.helpers.ast.nodes.Ast_Load import Ast_Load
|
47
|
+
from osbot_utils.helpers.ast.nodes.Ast_Lt import Ast_Lt
|
48
|
+
from osbot_utils.helpers.ast.nodes.Ast_LtE import Ast_LtE
|
49
|
+
from osbot_utils.helpers.ast.nodes.Ast_Mod import Ast_Mod
|
50
|
+
from osbot_utils.helpers.ast.nodes.Ast_Module import Ast_Module
|
51
|
+
from osbot_utils.helpers.ast.nodes.Ast_Mult import Ast_Mult
|
52
|
+
from osbot_utils.helpers.ast.nodes.Ast_Name import Ast_Name
|
53
|
+
from osbot_utils.helpers.ast.nodes.Ast_Not import Ast_Not
|
54
|
+
from osbot_utils.helpers.ast.nodes.Ast_Not_Eq import Ast_Not_Eq
|
55
|
+
from osbot_utils.helpers.ast.nodes.Ast_Not_In import Ast_Not_In
|
56
|
+
from osbot_utils.helpers.ast.nodes.Ast_Or import Ast_Or
|
57
|
+
from osbot_utils.helpers.ast.nodes.Ast_Pass import Ast_Pass
|
58
|
+
from osbot_utils.helpers.ast.nodes.Ast_Pow import Ast_Pow
|
59
|
+
from osbot_utils.helpers.ast.nodes.Ast_Raise import Ast_Raise
|
60
|
+
from osbot_utils.helpers.ast.nodes.Ast_Return import Ast_Return
|
61
|
+
from osbot_utils.helpers.ast.nodes.Ast_Set import Ast_Set
|
62
|
+
from osbot_utils.helpers.ast.nodes.Ast_Slice import Ast_Slice
|
63
|
+
from osbot_utils.helpers.ast.nodes.Ast_Starred import Ast_Starred
|
64
|
+
from osbot_utils.helpers.ast.nodes.Ast_Store import Ast_Store
|
65
|
+
from osbot_utils.helpers.ast.nodes.Ast_Sub import Ast_Sub
|
66
|
+
from osbot_utils.helpers.ast.nodes.Ast_Subscript import Ast_Subscript
|
67
|
+
from osbot_utils.helpers.ast.nodes.Ast_Try import Ast_Try
|
68
|
+
from osbot_utils.helpers.ast.nodes.Ast_Tuple import Ast_Tuple
|
69
|
+
from osbot_utils.helpers.ast.nodes.Ast_Unary_Op import Ast_Unary_Op
|
70
|
+
from osbot_utils.helpers.ast.nodes.Ast_While import Ast_While
|
71
|
+
from osbot_utils.helpers.ast.nodes.Ast_With import Ast_With
|
72
|
+
from osbot_utils.helpers.ast.nodes.Ast_With_Item import Ast_With_Item
|
73
|
+
from osbot_utils.helpers.ast.nodes.Ast_Yield import Ast_Yield
|
74
|
+
|
75
|
+
ast_types = {
|
76
|
+
ast.Add : Ast_Add ,
|
77
|
+
ast.alias : Ast_Alias ,
|
78
|
+
ast.And : Ast_And ,
|
79
|
+
ast.Assert : Ast_Assert ,
|
80
|
+
ast.Assign : Ast_Assign ,
|
81
|
+
ast.Attribute : Ast_Attribute ,
|
82
|
+
ast.arg : Ast_Argument ,
|
83
|
+
ast.arguments : Ast_Arguments ,
|
84
|
+
ast.AugAssign : Ast_Aug_Assign ,
|
85
|
+
ast.BinOp : Ast_Bin_Op ,
|
86
|
+
ast.BoolOp : Ast_Bool_Op ,
|
87
|
+
ast.Break : Ast_Break ,
|
88
|
+
ast.Call : Ast_Call ,
|
89
|
+
ast.ClassDef : Ast_Class_Def ,
|
90
|
+
ast.Compare : Ast_Compare ,
|
91
|
+
ast.Constant : Ast_Constant ,
|
92
|
+
ast.Continue : Ast_Continue ,
|
93
|
+
ast.comprehension : Ast_Comprehension ,
|
94
|
+
ast.Dict : Ast_Dict ,
|
95
|
+
ast.ExceptHandler : Ast_Except_Handler,
|
96
|
+
ast.Expr : Ast_Expr ,
|
97
|
+
ast.Eq : Ast_Eq ,
|
98
|
+
ast.Gt : Ast_Gt ,
|
99
|
+
ast.GtE : Ast_GtE ,
|
100
|
+
ast.Import : Ast_Import ,
|
101
|
+
ast.ImportFrom : Ast_Import_From ,
|
102
|
+
ast.In : Ast_In ,
|
103
|
+
ast.For : Ast_For ,
|
104
|
+
ast.FunctionDef : Ast_Function_Def ,
|
105
|
+
ast.GeneratorExp : Ast_Generator_Exp ,
|
106
|
+
ast.If : Ast_If ,
|
107
|
+
ast.IfExp : Ast_If_Exp ,
|
108
|
+
ast.Is : Ast_Is ,
|
109
|
+
ast.IsNot : Ast_Is_Not ,
|
110
|
+
ast.keyword : Ast_Keyword ,
|
111
|
+
ast.Lambda : Ast_Lambda ,
|
112
|
+
ast.List : Ast_List ,
|
113
|
+
ast.ListComp : Ast_List_Comp ,
|
114
|
+
ast.Lt : Ast_Lt ,
|
115
|
+
ast.LtE : Ast_LtE ,
|
116
|
+
ast.Load : Ast_Load ,
|
117
|
+
ast.Mod : Ast_Mod ,
|
118
|
+
ast.Module : Ast_Module ,
|
119
|
+
ast.Mult : Ast_Mult ,
|
120
|
+
ast.Name : Ast_Name ,
|
121
|
+
ast.Not : Ast_Not ,
|
122
|
+
ast.NotEq : Ast_Not_Eq ,
|
123
|
+
ast.NotIn : Ast_Not_In ,
|
124
|
+
ast.Or : Ast_Or ,
|
125
|
+
ast.Pass : Ast_Pass ,
|
126
|
+
ast.Pow : Ast_Pow ,
|
127
|
+
ast.Raise : Ast_Raise ,
|
128
|
+
ast.Return : Ast_Return ,
|
129
|
+
ast.Slice : Ast_Slice ,
|
130
|
+
ast.Starred : Ast_Starred ,
|
131
|
+
ast.Store : Ast_Store ,
|
132
|
+
ast.Set : Ast_Set ,
|
133
|
+
ast.Sub : Ast_Sub ,
|
134
|
+
ast.Subscript : Ast_Subscript ,
|
135
|
+
ast.Try : Ast_Try ,
|
136
|
+
ast.Tuple : Ast_Tuple ,
|
137
|
+
ast.UnaryOp : Ast_Unary_Op ,
|
138
|
+
ast.While : Ast_While ,
|
139
|
+
ast.With : Ast_With ,
|
140
|
+
ast.withitem : Ast_With_Item ,
|
141
|
+
ast.Yield : Ast_Yield ,
|
142
|
+
}
|
143
|
+
|
144
|
+
for key, value in ast_types.items():
|
145
|
+
type_registry.register(key, value)
|
@@ -1,6 +1,8 @@
|
|
1
|
-
from
|
1
|
+
from typing import Type
|
2
|
+
from osbot_utils.type_safe.Type_Safe__Base import Type_Safe__Base, type_str
|
2
3
|
|
3
4
|
class Type_Safe__List(Type_Safe__Base, list):
|
5
|
+
expected_type : Type
|
4
6
|
|
5
7
|
def __init__(self, expected_type, *args):
|
6
8
|
super().__init__(*args)
|
@@ -21,7 +23,7 @@ class Type_Safe__List(Type_Safe__Base, list):
|
|
21
23
|
try:
|
22
24
|
self.is_instance_of_type(item, self.expected_type)
|
23
25
|
except TypeError as e:
|
24
|
-
raise TypeError(f"In Type_Safe__List: Invalid type for item: {e}")
|
26
|
+
raise TypeError(f"In Type_Safe__List: Invalid type for item: {e}") from None
|
25
27
|
super().append(item)
|
26
28
|
|
27
29
|
|
@@ -185,5 +185,5 @@ class Type_Safe__Method:
|
|
185
185
|
base_type = expected_type
|
186
186
|
|
187
187
|
if not isinstance(param_value, base_type):
|
188
|
-
raise ValueError(f"Parameter '{param_name}' expected type {expected_type}, but got {type(param_value)}")
|
188
|
+
raise ValueError(f"Parameter '{param_name}' expected type {expected_type}, but got {type(param_value)}") from None
|
189
189
|
return True
|
@@ -9,6 +9,19 @@ class Type_Safe__Primitive:
|
|
9
9
|
cls.__primitive_base__ = base
|
10
10
|
break
|
11
11
|
|
12
|
+
def __add__(self, other):
|
13
|
+
"""Override addition/concatenation to maintain type safety"""
|
14
|
+
if self.__primitive_base__ is str: # For string concatenation
|
15
|
+
result = super().__add__(other) # Perform the operation
|
16
|
+
return type(self)(result) # Return instance of the safe type
|
17
|
+
else: # For numeric types (int, float)
|
18
|
+
result = super().__add__(other)
|
19
|
+
try:
|
20
|
+
return type(self)(result) # Try to create safe type
|
21
|
+
except (ValueError, TypeError):
|
22
|
+
return result # Fall back to primitive if constraints violated
|
23
|
+
|
24
|
+
|
12
25
|
def __eq__(self, other):
|
13
26
|
if type(self) is type(other): # Same type → compare values
|
14
27
|
return super().__eq__(other)
|
@@ -22,7 +35,30 @@ class Type_Safe__Primitive:
|
|
22
35
|
def __hash__(self): # Include type in hash to maintain hash/eq contract , This works for str, int, float subclasses
|
23
36
|
return hash((type(self).__name__, super().__hash__()))
|
24
37
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
38
|
+
def __radd__(self, other):
|
39
|
+
"""Reverse addition/concatenation for when safe type is on the right"""
|
40
|
+
if self.__primitive_base__ is str:
|
41
|
+
result = other + str(self) # Perform string concatenation
|
42
|
+
return type(self)(result) # Return instance of the safe type
|
43
|
+
else:
|
44
|
+
result = other + self.__primitive_base__(self)
|
45
|
+
try:
|
46
|
+
return type(self)(result)
|
47
|
+
except (ValueError, TypeError):
|
48
|
+
return result
|
49
|
+
|
50
|
+
def __str__(self): # Return the primitive string representation"""
|
51
|
+
if self.__primitive_base__ is float: # Format the value using the primitive type's string formatting
|
52
|
+
return format(float(self), '')
|
53
|
+
elif self.__primitive_base__ is int:
|
54
|
+
return format(int(self), '')
|
55
|
+
elif self.__primitive_base__ is str:
|
56
|
+
return str.__str__(self)
|
57
|
+
return super().__str__()
|
28
58
|
|
59
|
+
def __repr__(self): # Enhanced repr for debugging that shows type information
|
60
|
+
value_str = self.__str__()
|
61
|
+
if self.__primitive_base__ is str:
|
62
|
+
return f"{type(self).__name__}('{value_str}')"
|
63
|
+
else:
|
64
|
+
return f"{type(self).__name__}({value_str})"
|
@@ -5,10 +5,10 @@ class Type_Safe__Raise_Exception:
|
|
5
5
|
|
6
6
|
def type_mismatch_error(self, var_name: str, expected_type: type, actual_type: type) -> None: # Raises formatted error for type validation failures
|
7
7
|
exception_message = f"Invalid type for attribute '{var_name}'. Expected '{expected_type}' but got '{actual_type}'"
|
8
|
-
raise ValueError(exception_message)
|
8
|
+
raise ValueError(exception_message) from None
|
9
9
|
|
10
10
|
def immutable_type_error(self, var_name, var_type):
|
11
|
-
exception_message = f"variable '{var_name}' is defined as type '{var_type}' which is not supported by Type_Safe, with only the following immutable types being supported: '{IMMUTABLE_TYPES}'"
|
12
|
-
raise ValueError(exception_message)
|
11
|
+
exception_message = f"variable '{var_name}' is defined as type '{var_type}' which is not supported by Type_Safe, with only the following immutable types being supported: '{IMMUTABLE_TYPES}' and the following subclasses (int, float, str)"
|
12
|
+
raise ValueError(exception_message) from None
|
13
13
|
|
14
14
|
type_safe_raise_exception = Type_Safe__Raise_Exception()
|
@@ -289,7 +289,7 @@ class Type_Safe__Validation:
|
|
289
289
|
if self.obj_is_type_union_compatible(var_type, IMMUTABLE_TYPES) is False: # if var_type is not something like Optional[Union[int, str]]
|
290
290
|
if var_type not in IMMUTABLE_TYPES or type(var_type) not in IMMUTABLE_TYPES:
|
291
291
|
if not isinstance(var_type, EnumMeta):
|
292
|
-
if not issubclass(var_type, (int,str, float)):
|
292
|
+
if not (isinstance(var_type, type) and issubclass(var_type, (int,str, float))):
|
293
293
|
type_safe_raise_exception.immutable_type_error(var_name, var_type)
|
294
294
|
|
295
295
|
def validate_variable_type(self, var_name, var_type, var_value): # Validate type compatibility
|
@@ -25,7 +25,7 @@ class Type_Safe__Step__Init:
|
|
25
25
|
setattr(__self, key, value)
|
26
26
|
else:
|
27
27
|
raise ValueError(f"{__self.__class__.__name__} has no attribute '{key}' and cannot be assigned the value '{value}'. "
|
28
|
-
f"Use {__self.__class__.__name__}.__default_kwargs__() see what attributes are available")
|
28
|
+
f"Use {__self.__class__.__name__}.__default_kwargs__() see what attributes are available") from None
|
29
29
|
|
30
30
|
def convert_value_to_type_safe_objects(self, __self, key, value):
|
31
31
|
annotation = type_safe_annotations.obj_attribute_annotation(__self, key)
|
osbot_utils/version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
v2.
|
1
|
+
v2.64.0
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: osbot_utils
|
3
|
-
Version: 2.
|
3
|
+
Version: 2.64.0
|
4
4
|
Summary: OWASP Security Bot - Utils
|
5
5
|
License: MIT
|
6
6
|
Author: Dinis Cruz
|
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
|
|
23
23
|
|
24
24
|
Powerful Python util methods and classes that simplify common apis and tasks.
|
25
25
|
|
26
|
-

|
27
27
|
[](https://codecov.io/gh/owasp-sbot/OSBot-Utils)
|
28
28
|
|
29
29
|
|
@@ -16,7 +16,7 @@ osbot_utils/decorators/lists/index_by.py,sha256=Awk3ebtYVywxnb51V-ZGE8pd1fwd8J42
|
|
16
16
|
osbot_utils/decorators/methods/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
17
|
osbot_utils/decorators/methods/cache.py,sha256=IMmHBeR6qtaOfBNgZUeI1SVLoexQQy6vk1LDW-20_5w,1062
|
18
18
|
osbot_utils/decorators/methods/cache_on_function.py,sha256=sDebxWjJnusb_w4R26OTYcmTF6CCeWrpesn-dgzEu8g,2694
|
19
|
-
osbot_utils/decorators/methods/cache_on_self.py,sha256=
|
19
|
+
osbot_utils/decorators/methods/cache_on_self.py,sha256=KCdqQ9y11xKBAuXGK5TV40CVQBNgF-30-aYeTdktCS0,1698
|
20
20
|
osbot_utils/decorators/methods/cache_on_tmp.py,sha256=8wLnRAUUkHobu6-B2Q8aAE8jLeIu3b-CQuMtXcnIN9w,3102
|
21
21
|
osbot_utils/decorators/methods/capture_exception.py,sha256=r6d1TPibAVLL7B4I0oXtnHQwFFdLBZbDnytYWO6PxS4,1174
|
22
22
|
osbot_utils/decorators/methods/capture_status.py,sha256=5xklG0usO3hGTjedhrmIucXtUjPd2pkuvA5jsty0a5E,659
|
@@ -38,13 +38,13 @@ osbot_utils/helpers/Guid.py,sha256=0Ay3TYYk2nPr-JRVRCMFxbr8OvoQomv5HjT7o5B7cos,8
|
|
38
38
|
osbot_utils/helpers/Hashicorp_Secrets.py,sha256=e2fWWHK6bubpAm1sw5y8X5kh2Hk5d4JyZCnUovZip5A,4232
|
39
39
|
osbot_utils/helpers/Local_Cache.py,sha256=67qmeVXUBhfqLUQhjFBE9Pjt5dcjpQYhqCTGQNWgVSU,3232
|
40
40
|
osbot_utils/helpers/Local_Caches.py,sha256=LUeNJX07Ccnp1SIhYvhqqQFVF6r_Ez7bWha8ssomuvY,1957
|
41
|
-
osbot_utils/helpers/Obj_Id.py,sha256=
|
41
|
+
osbot_utils/helpers/Obj_Id.py,sha256=T6HF8khO7u1C688Ref_8Li2zxowRc9pIghUkMwb6H1c,1023
|
42
42
|
osbot_utils/helpers/Print_Table.py,sha256=LEXbyqGg_6WSraI4cob4bNNSu18ddqvALp1zGK7bPhs,19126
|
43
43
|
osbot_utils/helpers/Python_Audit.py,sha256=shpZlluJwqJBAlad6xN01FkgC1TsQ48RLvR5ZjmrKa4,1539
|
44
44
|
osbot_utils/helpers/Random_Guid.py,sha256=86xnlevsiitCzFB-VjjBmXXV14pbcKd67YWEDsYTCFk,483
|
45
45
|
osbot_utils/helpers/Random_Guid_Short.py,sha256=YP_k5OLuYvXWGU2OEnQHk_OGViBQofTWKm3pUdQaJao,404
|
46
46
|
osbot_utils/helpers/Random_Seed.py,sha256=14btja8LDN9cMGWaz4fCNcMRU_eyx49gas-_PQvHgy4,634
|
47
|
-
osbot_utils/helpers/Safe_Id.py,sha256=
|
47
|
+
osbot_utils/helpers/Safe_Id.py,sha256=Mj66QVcEHqUKDFb0cdl7cbMOlQKxfFuRxnupmKo6UG8,696
|
48
48
|
osbot_utils/helpers/Str_ASCII.py,sha256=PRqyu449XnKrLn6b9Miii1Hv-GO5OAa1UhhgvlRcC2M,704
|
49
49
|
osbot_utils/helpers/Timestamp_Now.py,sha256=OZwSwmYA1pZAnVglpx2lBL5t8Pu_HxzmYdNEJnmTqKI,536
|
50
50
|
osbot_utils/helpers/Type_Registry.py,sha256=Ajk3SyMSKDi2g9SJYUtTgg7PZkAgydaHcpbGuEN3S94,311
|
@@ -126,6 +126,12 @@ osbot_utils/helpers/ast/nodes/Ast_With.py,sha256=MO3doSFTxQEXUm8t4wDG5hFfWa_ByOM
|
|
126
126
|
osbot_utils/helpers/ast/nodes/Ast_With_Item.py,sha256=ger_t7_sEb2HiHpFwXBRAUJTCgXBfKZyv2uGuHMnQqA,260
|
127
127
|
osbot_utils/helpers/ast/nodes/Ast_Yield.py,sha256=7ATJnOHekV9XquMTYYSJ6xqyDvgwFc596myKPpHLddk,197
|
128
128
|
osbot_utils/helpers/ast/nodes/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
129
|
+
osbot_utils/helpers/cache_on_self/Cache_Controller.py,sha256=JN-kwLqxTXzrtRxZs9PJUKh4SGVE-bHx10wXw5QuEZE,1342
|
130
|
+
osbot_utils/helpers/cache_on_self/Cache_Key_Generator.py,sha256=3L4v7gDi2sLoegL3obwKPKbGzv3Woi19-laHrrDsvGw,4937
|
131
|
+
osbot_utils/helpers/cache_on_self/Cache_Metrics.py,sha256=8bGl2kCkJE9z2kLvcXk4WRd10Av2wxiNY6LP6Wg6WyY,1362
|
132
|
+
osbot_utils/helpers/cache_on_self/Cache_On_Self.py,sha256=IOQWXG_eJ6HjQQxRhXvU8dk4T7r9dsWxL5YMNOsZRrE,7100
|
133
|
+
osbot_utils/helpers/cache_on_self/Cache_Storage.py,sha256=4tl5P1ckzHsQhuSy1V1SO8wp-n-qCKW558xoIBpZo5E,1397
|
134
|
+
osbot_utils/helpers/cache_on_self/__init__.py,sha256=Q2hGnJ-3ql63uL7iJzCv3a9m8yrmaAAm6_hlFzSjcgE,8157
|
129
135
|
osbot_utils/helpers/cache_requests/Cache__Requests__Actions.py,sha256=PnCJdNltS8GGHSjmUMLF2u7zRT3Ym8M7DIT3n9LkJlw,1225
|
130
136
|
osbot_utils/helpers/cache_requests/Cache__Requests__Config.py,sha256=cGjtPB-K_0Wk4bKbS26A3Rg2HAcBhWlMoZE0isBpRdc,932
|
131
137
|
osbot_utils/helpers/cache_requests/Cache__Requests__Data.py,sha256=cWVViRSVbS_V7AYxSjR91Wg1UYPi--zbzGmnAZ6t8n0,5501
|
@@ -367,9 +373,9 @@ osbot_utils/testing/performance/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JC
|
|
367
373
|
osbot_utils/type_safe/Type_Safe.py,sha256=LOWRGOGpnRU2iTRLP47vUoyalK0vjFrWlnaghBIogWg,6267
|
368
374
|
osbot_utils/type_safe/Type_Safe__Base.py,sha256=UTMipTL6mXoetAEUCI5hs8RqXp4NDYOvoAiYoFOt5jg,8807
|
369
375
|
osbot_utils/type_safe/Type_Safe__Dict.py,sha256=QB200L5eNWT3FnUv8sm5kncj1wXJsJ9uRycNFl9xb6Y,3077
|
370
|
-
osbot_utils/type_safe/Type_Safe__List.py,sha256=
|
371
|
-
osbot_utils/type_safe/Type_Safe__Method.py,sha256=
|
372
|
-
osbot_utils/type_safe/Type_Safe__Primitive.py,sha256=
|
376
|
+
osbot_utils/type_safe/Type_Safe__List.py,sha256=y_lp7Ai0HfQCqC8Bxn0g6_M9MP5lPOXy5Dhkuj2fJvQ,1891
|
377
|
+
osbot_utils/type_safe/Type_Safe__Method.py,sha256=lHBCpW5nf2ikybm9oucvq6L67VXNO4qEvzGskJ1hGHs,15697
|
378
|
+
osbot_utils/type_safe/Type_Safe__Primitive.py,sha256=CJ4LP2W5i9utSSzuiiJrwqvwdMv1DeQ6dIZICtYfLTY,3635
|
373
379
|
osbot_utils/type_safe/Type_Safe__Set.py,sha256=j12fc8cbd9-s_a13ysaz723rNEW4Dt6hObCd0S-AjIg,1432
|
374
380
|
osbot_utils/type_safe/Type_Safe__Tuple.py,sha256=Kx7C4YfHybRbMmVMcmV6yFLi4T48pb592vEZfjjyLxo,1710
|
375
381
|
osbot_utils/type_safe/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -383,15 +389,15 @@ osbot_utils/type_safe/shared/Type_Safe__Convert.py,sha256=q1ds7AqgNhZX1pW0SNq_VN
|
|
383
389
|
osbot_utils/type_safe/shared/Type_Safe__Json_Compressor.py,sha256=TDbot_NNzCPXBQv0l5mksWueJNfxlVFDBGxIH8Jf_XY,5426
|
384
390
|
osbot_utils/type_safe/shared/Type_Safe__Json_Compressor__Type_Registry.py,sha256=wYOCg7F1nTrRn8HlnZvrs_8A8WL4gxRYRLnXZpGIiuk,1119
|
385
391
|
osbot_utils/type_safe/shared/Type_Safe__Not_Cached.py,sha256=25FAl6SOLxdStco_rm9tgOYLfuKyBWheGdl7vVa56UU,800
|
386
|
-
osbot_utils/type_safe/shared/Type_Safe__Raise_Exception.py,sha256=
|
392
|
+
osbot_utils/type_safe/shared/Type_Safe__Raise_Exception.py,sha256=TMO4uqPLzie79kSqPAP3s6Xa3LtBI_Sql1ou3-3_XBo,896
|
387
393
|
osbot_utils/type_safe/shared/Type_Safe__Shared__Variables.py,sha256=SuZGl9LryQX6IpOE0I_lbzClT-h17UNylC__-M8ltTY,129
|
388
|
-
osbot_utils/type_safe/shared/Type_Safe__Validation.py,sha256=
|
394
|
+
osbot_utils/type_safe/shared/Type_Safe__Validation.py,sha256=1XvbWJmRfyqBcdOTuYZ5fiItyMF0ttSPFnWjqZTGbYE,19542
|
389
395
|
osbot_utils/type_safe/shared/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
390
396
|
osbot_utils/type_safe/steps/Type_Safe__Step__Class_Kwargs.py,sha256=snoyJKvZ1crgF2fp0zexwNPnV_E63RfyRIsMAZdrKNY,6995
|
391
397
|
osbot_utils/type_safe/steps/Type_Safe__Step__Default_Kwargs.py,sha256=tzKXDUc0HVP5QvCWsmcPuuZodNvQZ9FeMDNI2x00Ngw,1943
|
392
398
|
osbot_utils/type_safe/steps/Type_Safe__Step__Default_Value.py,sha256=b5vsgM8eg9yq2KM0wRMntVHma6OhN_HnU76LxhEIpoA,4483
|
393
399
|
osbot_utils/type_safe/steps/Type_Safe__Step__From_Json.py,sha256=AguNcZwKKnyNY87bSAF1xLwYW5h_IDfUad70QqIiK3I,14622
|
394
|
-
osbot_utils/type_safe/steps/Type_Safe__Step__Init.py,sha256=
|
400
|
+
osbot_utils/type_safe/steps/Type_Safe__Step__Init.py,sha256=wBdShMavx8Ja5IACyW8dP3_fq_iqrMp8HHziOqjgxqU,4483
|
395
401
|
osbot_utils/type_safe/steps/Type_Safe__Step__Set_Attr.py,sha256=Nm1cleC-IDez6HFDejTfMprrnOfTvmEopO_pW6w1vFM,5507
|
396
402
|
osbot_utils/type_safe/steps/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
397
403
|
osbot_utils/type_safe/validators/Type_Safe__Validator.py,sha256=cJIPSBarjV716SZUOLvz7Mthjk-aUYKUQtRDtKUBmN4,779
|
@@ -426,8 +432,8 @@ osbot_utils/utils/Toml.py,sha256=Rxl8gx7mni5CvBAK-Ai02EKw-GwtJdd3yeHT2kMloik,166
|
|
426
432
|
osbot_utils/utils/Version.py,sha256=Ww6ChwTxqp1QAcxOnztkTicShlcx6fbNsWX5xausHrg,422
|
427
433
|
osbot_utils/utils/Zip.py,sha256=pR6sKliUY0KZXmqNzKY2frfW-YVQEVbLKiyqQX_lc-8,14052
|
428
434
|
osbot_utils/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
429
|
-
osbot_utils/version,sha256=
|
430
|
-
osbot_utils-2.
|
431
|
-
osbot_utils-2.
|
432
|
-
osbot_utils-2.
|
433
|
-
osbot_utils-2.
|
435
|
+
osbot_utils/version,sha256=Ik_Z1_68qStKaUrjNMdT-_2K34-svwqhzWn_iKWEiAM,8
|
436
|
+
osbot_utils-2.64.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
437
|
+
osbot_utils-2.64.0.dist-info/METADATA,sha256=NVqO8V4OXl0l6YXdNQCbsspZ4wle5_jgH97qimTST9M,1329
|
438
|
+
osbot_utils-2.64.0.dist-info/WHEEL,sha256=b4K_helf-jlQoXBBETfwnf4B04YC67LOev0jo4fX5m8,88
|
439
|
+
osbot_utils-2.64.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|