Skip to content

Commit fd1a09c

Browse files
committed
Merge dev into main
2 parents 2be5b7c + cf9f1bc commit fd1a09c

File tree

3 files changed

+60
-6
lines changed

3 files changed

+60
-6
lines changed

osbot_utils/type_safe/type_safe_core/decorators/type_safe.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import functools # For wrapping functions
1+
import functools # For wrapping functions
22
from osbot_utils.type_safe.Type_Safe__Base import Type_Safe__Base
3+
from osbot_utils.type_safe.Type_Safe__Primitive import Type_Safe__Primitive
34
from osbot_utils.type_safe.type_safe_core.methods.Type_Safe__Method import Type_Safe__Method
45

56

@@ -21,6 +22,8 @@ def wrapper(*args, **kwargs):
2122
result = func(**bound_args.arguments) # Call original function
2223

2324
if return_type is not None and result is not None: # Validate return type using existing type checking infrastructure
25+
if isinstance(return_type, type) and issubclass(return_type, Type_Safe__Primitive): # Try to convert Type_Safe__Primitive types
26+
result = return_type(result) # Since we are using a Type_Safe__Primitive, if there is bad data (like a negative number in Safe_UInt) this will trigger an exception
2427
try:
2528
validator.is_instance_of_type(result, return_type)
2629
except TypeError as e:

tests/unit/type_safe/type_safe_core/decorators/test__decorator__type_safe.py

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from unittest import TestCase
55
from typing import Union, Optional, List, Type, Callable, Any
66
from dataclasses import dataclass
7+
from osbot_utils.type_safe.primitives.core.Safe_UInt import Safe_UInt
78
from osbot_utils.type_safe.primitives.domains.identifiers.Obj_Id import Obj_Id
89
from osbot_utils.type_safe.primitives.domains.cryptography.safe_str.Safe_Str__Hash import Safe_Str__Hash
910
from osbot_utils.type_safe.primitives.domains.files.safe_str.Safe_Str__File__Name import Safe_Str__File__Name
@@ -371,20 +372,20 @@ def return_wrong_type() -> int:
371372
with pytest.raises(TypeError, match="return type validation failed"):
372373
return_wrong_type()
373374

374-
def test_return_type_validation__type_safe_primitives(self):
375-
"""Test return type validation with Type_Safe primitives"""
375+
def test_return_type_validation__type_safe_primitives(self): # Test return type validation with Type_Safe primitives
376376

377377
@type_safe
378378
def return_safe_id() -> Safe_Id:
379379
return Safe_Id("test")
380380

381381
@type_safe
382382
def return_wrong_primitive() -> Safe_Id:
383-
return "not a safe_id"
383+
return "not a safe_id" * 50 # breaks Safe max size of 512
384384

385385
assert type(return_safe_id()) is Safe_Id
386386

387-
with pytest.raises(TypeError, match="return type validation failed"):
387+
error_message = "Invalid ID: The ID must not exceed 512 characters (was 650)."
388+
with pytest.raises(ValueError, match=re.escape(error_message)):
388389
return_wrong_primitive()
389390

390391
def test_return_type_validation__optional(self):
@@ -533,6 +534,44 @@ def process_wrong(value: int) -> str:
533534
with pytest.raises(ValueError, match="Parameter 'value' expected type"):
534535
process("not an int")
535536

537+
def test__type_safe_decorator__converts_return_primitives(self): # Test that return values are converted to Type_Safe__Primitive types
538+
539+
@type_safe
540+
def returns_safe_uint() -> Safe_UInt:
541+
return 42 # int should convert to Safe_UInt
542+
543+
result = returns_safe_uint()
544+
assert isinstance(result, Safe_UInt)
545+
assert result == 42
546+
547+
@type_safe
548+
def returns_safe_str() -> Safe_Str:
549+
return "hello" # str should convert to Safe_Str
550+
551+
result = returns_safe_str()
552+
assert isinstance(result, Safe_Str)
553+
assert result == "hello"
554+
555+
556+
def test__type_safe_decorator__validates_converted_return_values(self): # Test that converted values are still validated against constraints
557+
558+
@type_safe
559+
def returns_constrained_uint() -> Safe_UInt:
560+
return -1 # Should fail Safe_UInt's min_value=0 constraint
561+
562+
error_message = 'Safe_UInt must be >= 0, got -1'
563+
with pytest.raises(ValueError, match=re.escape(error_message)):
564+
returns_constrained_uint()
565+
566+
567+
def test__type_safe_decorator__return_none_with_optional(self): # Test that None is valid for Optional return types
568+
569+
@type_safe
570+
def maybe_returns_uint() -> Optional[Safe_UInt]:
571+
return None
572+
573+
result = maybe_returns_uint()
574+
assert result is None
536575

537576

538577

tests/unit/type_safe/type_safe_core/decorators/test__decorator__type_safe__regression.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from typing import Any, Dict, Optional, Type, List
55
from unittest import TestCase
66
from osbot_utils.type_safe.primitives.core.Safe_Int import Safe_Int
7+
from osbot_utils.type_safe.primitives.core.Safe_UInt import Safe_UInt
78
from osbot_utils.type_safe.primitives.domains.files.safe_str.Safe_Str__File__Path import Safe_Str__File__Path
89
from osbot_utils.type_safe.primitives.domains.identifiers.Safe_Id import Safe_Id
910
from osbot_utils.type_safe.primitives.domains.identifiers.safe_int.Timestamp_Now import Timestamp_Now
@@ -375,4 +376,15 @@ def method_2(self, value: Any, node_type: type = None):
375376
with pytest.raises(ValueError, match=expected_error):
376377
assert An_Class().method_1('a', int) # Fixed was: == {'value': True, 'node_type': int} # BUG, value should be 'a'
377378

378-
assert An_Class().method_2('a', int) == {'node_type': int, 'value': 'a'} # Fixed
379+
assert An_Class().method_2('a', int) == {'node_type': int, 'value': 'a'} # Fixed
380+
381+
def test__regression__type_save_method__return_value_cast_to_safe_uint(self):
382+
@type_safe
383+
def an_method() -> Safe_UInt:
384+
return 42
385+
386+
#error_message = "Function 'test__decorator__type_safe__bugs.test__bug__type_save_method__return_value_cast_to_safe_uint.<locals>.an_method' return type validation failed: Expected 'Safe_UInt', but got 'int'"
387+
# with pytest.raises(TypeError, match=re.escape(error_message)):
388+
# an_method() # BUG: we should handle transparently the conversion into Safe_UInt (and only raise an exeception if the data is bad)
389+
assert an_method() == 42
390+
assert type(an_method()) is Safe_UInt

0 commit comments

Comments
 (0)