Coverage for sources/classcore/utilities.py: 100%

60 statements  

« prev     ^ index     » next       coverage.py v7.9.0, created at 2025-06-12 01:35 +0000

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

4#============================================================================# 

5# # 

6# Licensed under the Apache License, Version 2.0 (the "License"); # 

7# you may not use this file except in compliance with the License. # 

8# You may obtain a copy of the License at # 

9# # 

10# http://www.apache.org/licenses/LICENSE-2.0 # 

11# # 

12# Unless required by applicable law or agreed to in writing, software # 

13# distributed under the License is distributed on an "AS IS" BASIS, # 

14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # 

15# See the License for the specific language governing permissions and # 

16# limitations under the License. # 

17# # 

18#============================================================================# 

19 

20 

21''' Various utilities for class manipulation. ''' 

22 

23 

24from . import __ 

25 

26 

27def describe_object( objct: object, / ) -> str: 

28 ''' Returns object type with fully-qualified name. ''' 

29 if __.inspect.isclass( objct ): 

30 return "class '{}'".format( qualify_class_name( objct ) ) 

31 # TODO? functions, methods, etc... 

32 return "instance of {}".format( describe_object( type( objct ) ) ) 

33 

34 

35def getattr0( 

36 objct: object, /, name: str, default: __.typx.Any 

37) -> __.typx.Any: 

38 ''' Returns special private attribute from object. 

39 

40 This avoids inheritance-related collisions. 

41 

42 Uses mangled attribute name which is unique to the class, 

43 except when attribute is slotted. Slotted attributes are effectively 

44 isolated from inheritance. 

45 ''' 

46 if not __.inspect.isclass( objct ): 

47 for base in type( objct ).mro( ): 

48 slots = getattr( base, '__slots__', ( ) ) 

49 if name in slots: return getattr( objct, name, default ) 

50 name_m = mangle_name( objct, name ) 

51 return getattr( objct, name_m, default ) 

52 

53 

54def delattr0( objct: object, /, name: str ) -> None: 

55 ''' Deletes special private attribute on object. 

56 

57 This avoids inheritance-related collisions. 

58 

59 Uses mangled attribute name which is unique to the class, 

60 except when attribute is slotted. Slotted attributes are effectively 

61 isolated from inheritance. 

62 ''' 

63 if not __.inspect.isclass( objct ): 

64 for base in type( objct ).mro( ): 

65 slots = getattr( base, '__slots__', ( ) ) 

66 if name in slots: 

67 delattr( objct, name ) 

68 return 

69 name_m = mangle_name( objct, name ) 

70 delattr( objct, name_m ) 

71 

72 

73def setattr0( objct: object, /, name: str, value: __.typx.Any ) -> None: 

74 ''' Assigns special private attribute to object. 

75 

76 This avoids inheritance-related collisions. 

77 

78 Uses mangled attribute name which is unique to the class, 

79 except when attribute is slotted. Slotted attributes are effectively 

80 isolated from inheritance. 

81 ''' 

82 if not __.inspect.isclass( objct ): 

83 for base in type( objct ).mro( ): 

84 slots = getattr( base, '__slots__', ( ) ) 

85 if name in slots: 

86 setattr( objct, name, value ) 

87 return 

88 name_m = mangle_name( objct, name ) 

89 setattr( objct, name_m, value ) 

90 

91 

92def mangle_name( objct: object, /, name: str ) -> str: 

93 ''' Mangles attribute name so that it is unique. 

94 

95 Effectively provides name of private member attribute, 

96 which is unique across class inheritance. 

97 ''' 

98 if not __.inspect.isclass( objct ): 

99 return mangle_name( type( objct ), name ) 

100 namehash = __.hashlib.sha256( ) 

101 namehash.update( qualify_class_name( objct ).encode( ) ) 

102 namehash_hex = namehash.hexdigest( ) 

103 return f"{name}_{namehash_hex}" 

104 

105 

106def qualify_class_name( cls: type ) -> str: 

107 ''' Returns fully-qualified class name. ''' 

108 return f"{cls.__module__}.{cls.__qualname__}" 

109 

110 

111def repair_class_reproduction( original: type, reproduction: type ) -> None: 

112 ''' Repairs a class reproduction, if necessary. ''' 

113 match __.platform.python_implementation( ): 

114 case 'CPython': # pragma: no branch 

115 _repair_cpython_class_closures( original, reproduction ) 

116 case _: pass # pragma: no cover 

117 

118 

119def _repair_cpython_class_closures( 

120 original: type, reproduction: type 

121) -> None: 

122 # Adapted from https://github.com/python/cpython/pull/124455/files 

123 def try_repair_closure( 

124 function: __.cabc.Callable[ ..., __.typx.Any ] 

125 ) -> bool: 

126 try: index = function.__code__.co_freevars.index( '__class__' ) 

127 except ValueError: return False 

128 if not function.__closure__: return False # pragma: no branch 

129 closure = function.__closure__[ index ] 

130 if original is closure.cell_contents: # pragma: no branch 

131 closure.cell_contents = reproduction 

132 return True 

133 return False # pragma: no cover 

134 

135 for attribute in reproduction.__dict__.values( ): 

136 attribute_ = __.inspect.unwrap( attribute ) 

137 if ( __.inspect.isfunction( attribute_ ) 

138 and try_repair_closure( attribute_ ) 

139 ): return 

140 if isinstance( attribute_, property ): 

141 for aname in ( 'fget', 'fset', 'fdel' ): 

142 accessor = getattr( attribute_, aname ) 

143 if None is accessor: continue 

144 if try_repair_closure( accessor ): return