Skip to content

Commit

Permalink
removed Type_Safe 'auto feature' of trying to find get_ and set_ meth…
Browse files Browse the repository at this point in the history
…ods (this is better done outside of this class)
  • Loading branch information
DinisCruz committed Jan 12, 2025
1 parent 9b1e275 commit 7d13fc2
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 66 deletions.
34 changes: 17 additions & 17 deletions osbot_utils/type_safe/Type_Safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,23 +65,23 @@ def __init__(self, **kwargs):
def __enter__(self): return self
def __exit__(self, exc_type, exc_val, exc_tb): pass

def __getattr__(self, name): # Called when an attribute is not found through normal attribute access
if name.startswith(("set_", "get_")): # Check if the requested attribute is a getter or setter method
prefix = name[:4] # Extract "set_" or "get_" from the method name
attr_name = name[4:] # Get the actual attribute name by removing the prefix

if hasattr(self, attr_name): # Verify that the target attribute actually exists on the object
if prefix == "set_": # Handle setter method creation
def setter(value): # Create a dynamic setter function that takes a value parameter
setattr(self, attr_name, value) # Set the attribute value using type-safe setattr from Type_Safe
return self # Return self for method chaining
return setter # Return the setter function
else: # get_ # Handle getter method creation
def getter(): # Create a dynamic getter function with no parameters
return getattr(self, attr_name) # Return the attribute value using Python's built-in getattr
return getter # Return the getter function

raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") # Raise error if attribute is not a valid getter/setter
# def __getattr__(self, name): # Called when an attribute is not found through normal attribute access
# if name.startswith(("set_", "get_")): # Check if the requested attribute is a getter or setter method
# prefix = name[:4] # Extract "set_" or "get_" from the method name
# attr_name = name[4:] # Get the actual attribute name by removing the prefix
#
# if hasattr(self, attr_name): # Verify that the target attribute actually exists on the object
# if prefix == "set_": # Handle setter method creation
# def setter(value): # Create a dynamic setter function that takes a value parameter
# setattr(self, attr_name, value) # Set the attribute value using type-safe setattr from Type_Safe
# return self # Return self for method chaining
# return setter # Return the setter function
# else: # get_ # Handle getter method creation
# def getter(): # Create a dynamic getter function with no parameters
# return getattr(self, attr_name) # Return the attribute value using Python's built-in getattr
# return getter # Return the getter function
#
# raise AttributeError(f"'{type(self).__name__}' object has no attribute '{name}'") # Raise error if attribute is not a valid getter/setter

def __setattr__(self, name, value):
from osbot_utils.utils.Objects import convert_dict_to_value_from_obj_annotation
Expand Down
99 changes: 50 additions & 49 deletions tests/unit/type_safe/test_Type_Safe.py
Original file line number Diff line number Diff line change
Expand Up @@ -616,55 +616,56 @@ class Target_Class(Base_Class):
merged_class.an_int= 456 # confirm that change in merged_class
assert base_class.an_int == 456 # impacts base_class

def test___supports_automatic_getters_and_setters_for_attributes(self):
class An_Class(Type_Safe):
an_str : str
an_int : int
an_list : list
_private : str # Test private attribute behavior

an_class = An_Class()

# Test basic getter/setter functionality
assert an_class.set_an_str('abc') == an_class
assert an_class.get_an_str() == 'abc'
assert an_class.json() == {'an_int': 0, 'an_list': [], 'an_str': 'abc', '_private': ''}

# Test method chaining
assert an_class.set_an_int(123).set_an_str('def').get_an_str() == 'def'
assert an_class.get_an_int() == 123

# Test list attribute
test_list = [1, 2, 3]
assert an_class.set_an_list(test_list) == an_class
assert an_class.get_an_list() == test_list

# Test None assignments
with pytest.raises(ValueError, match="Can't set None, to a variable that is already set. Invalid type for attribute 'an_str'. Expected '<class 'str'>' but got '<class 'NoneType'>'"):
assert an_class.set_an_str(None) == an_class
assert an_class.get_an_str() == 'def' # confirm value has not been changed

# Test private attribute access
assert an_class.set__private("secret") == an_class
assert an_class.get__private() == "secret"

# Test error cases
with pytest.raises(AttributeError, match="'An_Class' object has no attribute 'set_an_aaa'"):
an_class.set_an_aaa()
with pytest.raises(AttributeError, match="'An_Class' object has no attribute 'get_an_aaa'"):
an_class.get_an_aaa()
with pytest.raises(AttributeError, match="'An_Class' object has no attribute 'aaaaaaaaaa'"):
an_class.aaaaaaaaaa()
with pytest.raises(ValueError, match="Invalid type for attribute 'an_str'. Expected '<class 'str'>' but got '<class 'int'>"):
an_class.set_an_str(123)
with pytest.raises(ValueError, match="Invalid type for attribute 'an_int'. Expected '<class 'int'>' but got '<class 'str'>"):
an_class.set_an_int('abc')

# Test edge cases
with pytest.raises(AttributeError):
an_class.get_() # Empty attribute name
with pytest.raises(AttributeError):
an_class.set_() # Empty attribute name
# not supported anymore (it was a good idea, but this is better done with set_as_property)
# def test___supports_automatic_getters_and_setters_for_attributes(self):
# class An_Class(Type_Safe):
# an_str : str
# an_int : int
# an_list : list
# _private : str # Test private attribute behavior
#
# an_class = An_Class()
#
# # Test basic getter/setter functionality
# assert an_class.set_an_str('abc') == an_class
# assert an_class.get_an_str() == 'abc'
# assert an_class.json() == {'an_int': 0, 'an_list': [], 'an_str': 'abc', '_private': ''}
#
# # Test method chaining
# assert an_class.set_an_int(123).set_an_str('def').get_an_str() == 'def'
# assert an_class.get_an_int() == 123
#
# # Test list attribute
# test_list = [1, 2, 3]
# assert an_class.set_an_list(test_list) == an_class
# assert an_class.get_an_list() == test_list
#
# # Test None assignments
# with pytest.raises(ValueError, match="Can't set None, to a variable that is already set. Invalid type for attribute 'an_str'. Expected '<class 'str'>' but got '<class 'NoneType'>'"):
# assert an_class.set_an_str(None) == an_class
# assert an_class.get_an_str() == 'def' # confirm value has not been changed
#
# # Test private attribute access
# assert an_class.set__private("secret") == an_class
# assert an_class.get__private() == "secret"
#
# # Test error cases
# with pytest.raises(AttributeError, match="'An_Class' object has no attribute 'set_an_aaa'"):
# an_class.set_an_aaa()
# with pytest.raises(AttributeError, match="'An_Class' object has no attribute 'get_an_aaa'"):
# an_class.get_an_aaa()
# with pytest.raises(AttributeError, match="'An_Class' object has no attribute 'aaaaaaaaaa'"):
# an_class.aaaaaaaaaa()
# with pytest.raises(ValueError, match="Invalid type for attribute 'an_str'. Expected '<class 'str'>' but got '<class 'int'>"):
# an_class.set_an_str(123)
# with pytest.raises(ValueError, match="Invalid type for attribute 'an_int'. Expected '<class 'int'>' but got '<class 'str'>"):
# an_class.set_an_int('abc')
#
# # Test edge cases
# with pytest.raises(AttributeError):
# an_class.get_() # Empty attribute name
# with pytest.raises(AttributeError):
# an_class.set_() # Empty attribute name

def test__type_assignments_and_validation(self): # Test simple type assignment with 'type' annotation
class Simple_Type(Type_Safe):
Expand Down

0 comments on commit 7d13fc2

Please sign in to comment.