Coverage for Users / vladimirpavlov / PycharmProjects / parameterizable / tests / test_cacheable_properties_mixin_wrapping.py: 94%

50 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-01-01 16:37 -0600

1from functools import cached_property 

2from mixinforge import CacheablePropertiesMixin 

3 

4def test_wrapped_descriptor(): 

5 """Test that cached_property discovery works through __wrapped__ chains.""" 

6 # Simulate a decorator that wraps the descriptor 

7 def descriptor_wrapper(descriptor): 

8 class Wrapper: 

9 def __init__(self, wrapped): 

10 self.__wrapped__ = wrapped 

11 def __set_name__(self, owner, name): 

12 if hasattr(self.__wrapped__, "__set_name__"): 

13 self.__wrapped__.__set_name__(owner, name) 

14 def __get__(self, instance, owner): 

15 return self.__wrapped__.__get__(instance, owner) 

16 return Wrapper(descriptor) 

17 

18 class Wrapped(CacheablePropertiesMixin): 

19 @descriptor_wrapper 

20 @cached_property 

21 def wrapped_prop(self): 

22 return "wrapped" 

23 

24 w = Wrapped() 

25 # Check discovery 

26 assert "wrapped_prop" in w._all_cached_properties_names 

27 

28 # Check functionality (invalidation relies on name only, so it should work 

29 # as long as the property puts the value in __dict__ under the same name) 

30 # Note: cached_property stores value in __dict__[name]. 

31 # Our wrapper delegates __get__, so cached_property.__get__ executes. 

32 # cached_property.__get__ writes to __dict__['wrapped_prop']. 

33 

34 assert w.wrapped_prop == "wrapped" 

35 assert "wrapped_prop" in w.__dict__ 

36 

37 w._invalidate_cache() 

38 assert "wrapped_prop" not in w.__dict__ 

39 assert w.wrapped_prop == "wrapped" 

40 

41def test_deep_wrapping_chain(): 

42 """Test that __wrapped__ unwrapping respects the depth limit.""" 

43 # Create a wrapper that creates a chain of given depth 

44 def create_deep_wrapper(wrapped_obj, depth): 

45 """Create a chain of wrappers with __wrapped__ attributes.""" 

46 current = wrapped_obj 

47 for _ in range(depth): 

48 class Wrapper: 

49 def __init__(self, inner): 

50 self.__wrapped__ = inner 

51 def __get__(self, instance, owner): 

52 if hasattr(self.__wrapped__, '__get__'): 

53 return self.__wrapped__.__get__(instance, owner) 

54 return self.__wrapped__ 

55 current = Wrapper(current) 

56 return current 

57 

58 class DeepWrapped(CacheablePropertiesMixin): 

59 pass 

60 

61 # Manually add wrapped properties to the class 

62 prop_shallow = cached_property(lambda self: "shallow") 

63 prop_shallow.__set_name__(DeepWrapped, "shallow_wrapped") 

64 DeepWrapped.shallow_wrapped = create_deep_wrapper(prop_shallow, 50) 

65 

66 prop_deep = cached_property(lambda self: "deep") 

67 prop_deep.__set_name__(DeepWrapped, "deep_wrapped") 

68 DeepWrapped.deep_wrapped = create_deep_wrapper(prop_deep, 150) 

69 

70 d = DeepWrapped() 

71 names = d._all_cached_properties_names 

72 

73 # shallow_wrapped should be discovered (within depth limit of 100) 

74 assert "shallow_wrapped" in names 

75 

76 # deep_wrapped should NOT be discovered (exceeds depth limit of 100) 

77 assert "deep_wrapped" not in names