Skip to content

Commit 6dd23df

Browse files
committed
major fix to the .obj() testing helper, where now it creates valid python identifiers :)
refactored .obj() related methods into a new class called __helpers() added new Safe_Str__Python__Identifier (which is used by .obj() to create the safe identifier name) added tests for test_Safe_Str__Python__Identifier and test_Safe_Str__Python__Module (which were missing) fixed ton of references that broke since the import of __ from the Objects class was quite widely used
1 parent ca7158a commit 6dd23df

File tree

66 files changed

+857
-143
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+857
-143
lines changed

osbot_utils/decorators/methods/required_fields.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
from functools import wraps
2-
1+
from functools import wraps
32
from osbot_utils.utils.Objects import get_missing_fields
43

54

osbot_utils/helpers/ast/nodes/Ast_Call.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
from osbot_utils.utils.Objects import obj_info
21
from osbot_utils.helpers.ast.Ast_Node import Ast_Node
32

43
class Ast_Call(Ast_Node):

osbot_utils/helpers/cache_requests/Cache__Requests__Data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import types
2-
from osbot_utils.type_safe.Type_Safe import Type_Safe
2+
from osbot_utils.type_safe.Type_Safe import Type_Safe
33
from osbot_utils.helpers.cache_requests.Cache__Requests__Config import Cache__Requests__Config
44
from osbot_utils.helpers.cache_requests.Cache__Requests__Table import Cache__Requests__Table
55
from osbot_utils.utils.Json import json_dumps, json_loads

osbot_utils/helpers/sqlite/tables/Sqlite__Table__Config.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
1+
from osbot_utils.base_classes.Kwargs_To_Self import Kwargs_To_Self
22
from osbot_utils.decorators.methods.cache_on_self import cache_on_self
3-
from osbot_utils.helpers.sqlite.Sqlite__Table import Sqlite__Table
4-
from osbot_utils.utils.Objects import pickle_save_to_bytes, pickle_load_from_bytes
3+
from osbot_utils.helpers.sqlite.Sqlite__Table import Sqlite__Table
4+
from osbot_utils.utils.Objects import pickle_save_to_bytes, pickle_load_from_bytes
55

66
SQLITE__TABLE_NAME__CONFIG = 'config'
77

osbot_utils/testing/__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
__MISSING__ = object()
66

77

8-
9-
108
class __(SimpleNamespace):
119
def __enter__(self) : return self
1210
def __exit__(self, exc_type, exc_val, exc_tb): return False
@@ -19,7 +17,7 @@ def __eq__(self, other):
1917
return super().__eq__(other)
2018

2119
for key in set(self.__dict__.keys()) | set(other.__dict__.keys()):
22-
self_val = getattr(self, key, None)
20+
self_val = getattr(self, key, None)
2321
other_val = getattr(other, key, None)
2422

2523
if self_val is __SKIP__ or other_val is __SKIP__: # Skip comparison if either value is a skip marker

osbot_utils/testing/__helpers.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import json
2+
from types import SimpleNamespace
3+
from osbot_utils.testing.__ import __
4+
from collections.abc import Mapping
5+
from osbot_utils.type_safe.primitives.domains.identifiers.safe_str.Safe_Str__Python__Identifier import Safe_Str__Python__Identifier
6+
7+
def dict_to_obj(target):
8+
if isinstance(target, Mapping):
9+
new_dict = {}
10+
for key, value in target.items(): # Sanitize the key to ensure it's a valid Python identifier
11+
safe_key = str(Safe_Str__Python__Identifier(str(key)))
12+
new_dict[safe_key] = dict_to_obj(value) # Recursively convert elements in the dict
13+
return __(**new_dict)
14+
elif isinstance(target, list): # Recursively convert elements in the list
15+
return [dict_to_obj(item) for item in target]
16+
elif isinstance(target, tuple): # Recursively convert elements in the tuple
17+
return tuple(dict_to_obj(item) for item in target)
18+
19+
return target
20+
21+
def obj_to_dict(target): # Recursively converts an object (SimpleNamespace) back into a dictionary."""
22+
if isinstance(target, SimpleNamespace): # Convert SimpleNamespace attributes to a dictionary
23+
return {key: obj_to_dict(value) for key, value in target.__dict__.items()}
24+
elif isinstance(target, list): # Handle lists: convert each item in the list
25+
return [obj_to_dict(item) for item in target]
26+
elif isinstance(target, tuple): # Handle tuples: convert each item and return as a tuple
27+
return tuple(obj_to_dict(item) for item in target)
28+
elif isinstance(target, set): # Handle sets: convert each item and return as a set
29+
return {obj_to_dict(item) for item in target}
30+
return target # Return non-object types as is
31+
32+
def str_to_obj(target):
33+
if hasattr(target, 'json'):
34+
return dict_to_obj(target.json())
35+
return dict_to_obj(json.loads(target))
36+
37+
38+
json_to_obj = str_to_obj
39+
obj = dict_to_obj

osbot_utils/type_safe/Type_Safe.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,7 @@ def update_from_kwargs(self, **kwargs): # Update instanc
9393
return self
9494

9595
def obj(self):
96-
from osbot_utils.utils.Objects import dict_to_obj
97-
96+
from osbot_utils.testing.__helpers import dict_to_obj
9897
return dict_to_obj(self.json())
9998

10099
def serialize_to_dict(self): # todo: see if we need this method or if the .json() is enough

osbot_utils/type_safe/Type_Safe__Base.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import types
22
from typing import get_args, Union, Optional, Any, ForwardRef, Literal
33
from osbot_utils.utils.Dev import pprint
4-
from osbot_utils.utils.Objects import obj
54
from osbot_utils.type_safe.primitives.domains.identifiers.Obj_Id import Obj_Id
65
from osbot_utils.type_safe.type_safe_core.shared.Type_Safe__Cache import type_safe_cache
76

@@ -144,6 +143,7 @@ def print_obj(self):
144143
pprint(self.obj())
145144

146145
def obj(self):
146+
from osbot_utils.testing.__helpers import obj
147147
return obj(self.json())
148148

149149
# todo: see if we should/can move this to the Objects.py file

osbot_utils/type_safe/Type_Safe__Primitive.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def __to_primitive__(self): # Convert this T
7777
return str(self) # fallback
7878

7979
def obj(self): # Get configuration as namespace object
80-
from osbot_utils.utils.Objects import dict_to_obj
80+
from osbot_utils.testing.__helpers import dict_to_obj
8181
return dict_to_obj(self.__cls_kwargs__())
8282

8383
def __cls_kwargs__(self): # Get class-level kwargs (configuration)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import re
2+
import keyword
3+
from osbot_utils.type_safe.primitives.core.Safe_Str import Safe_Str
4+
5+
class Safe_Str__Python__Identifier(Safe_Str):
6+
"""
7+
Ensures strings are valid Python identifiers.
8+
9+
Rules:
10+
- Must start with letter or underscore
11+
- Can only contain letters, numbers, underscores
12+
- Cannot be a Python keyword
13+
14+
Transformations:
15+
- Converts invalid starting characters (e.g., '8b1a...' → '_8b1a...')
16+
- Replaces invalid characters with underscores
17+
- Handles Python keywords by prefixing with underscore
18+
19+
Examples:
20+
'8b1a9953c4' → '_8b1a9953c4'
21+
'my-var' → 'my_var'
22+
'class' → '_class'
23+
"""
24+
max_length = 255
25+
regex = re.compile(r'[^a-zA-Z0-9_]')
26+
replacement_char = '_'
27+
to_lower_case = False
28+
trim_whitespace = True
29+
30+
def __new__(cls, value: str = None):
31+
instance = super().__new__(cls, value)
32+
result = str(instance)
33+
34+
if not result:
35+
return str.__new__(cls, '_')
36+
37+
# Ensure starts with letter or underscore
38+
if not (result[0].isalpha() or result[0] == '_'):
39+
result = '_' + result
40+
41+
# Check if it's a Python keyword
42+
43+
if keyword.iskeyword(result):
44+
result = '_' + result
45+
46+
return str.__new__(cls, result)

0 commit comments

Comments
 (0)