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

44 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-05 22:28 +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( obj: object ) -> str: 

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

29 if __.inspect.isclass( obj ): 

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

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

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

33 

34 

35def getattr0( obj: object, name: str, default: __.typx.Any ) -> __.typx.Any: 

36 ''' Returns private attribute from object. 

37 

38 Uses mangled attribute name which is unique to the class. 

39 ''' 

40 name_m = mangle_name( obj, name ) 

41 return getattr( obj, name_m, default ) 

42 

43 

44def delattr0( obj: object, name: str ) -> None: 

45 ''' Deletes private attribute on object. 

46 

47 Uses mangled attribute name which is unique to the class. 

48 ''' 

49 name_m = mangle_name( obj, name ) 

50 delattr( obj, name_m ) 

51 

52 

53def setattr0( obj: object, name: str, value: __.typx.Any ) -> None: 

54 ''' Assigns private attribute to object. 

55 

56 Uses mangled attribute name which is unique to the class. 

57 ''' 

58 name_m = mangle_name( obj, name ) 

59 setattr( obj, name_m, value ) 

60 

61 

62def mangle_name( obj: object, name: str ) -> str: 

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

64 

65 Effectively provides name of private member attribute, 

66 which is unique across class inheritance. 

67 ''' 

68 if not __.inspect.isclass( obj ): 

69 return mangle_name( type( obj ), name ) 

70 namehash = __.hashlib.sha256( ) 

71 namehash.update( qualify_class_name( obj ).encode( ) ) 

72 namehash_hex = namehash.hexdigest( ) 

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

74 

75 

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

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

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

79 

80 

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

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

83 match __.platform.python_implementation( ): 

84 case 'CPython': # pragma: no branch 

85 _repair_cpython_class_closures( original, reproduction ) 

86 case _: pass # pragma: no cover 

87 

88 

89def _repair_cpython_class_closures( 

90 original: type, reproduction: type 

91) -> None: 

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

93 def try_repair_closure( 

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

95 ) -> bool: 

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

97 except ValueError: return False 

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

99 closure = function.__closure__[ index ] 

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

101 closure.cell_contents = reproduction 

102 return True 

103 return False # pragma: no cover 

104 

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

106 attribute_ = __.inspect.unwrap( attribute ) 

107 if ( __.inspect.isfunction( attribute_ ) 

108 and try_repair_closure( attribute_ ) 

109 ): return 

110 if isinstance( attribute_, property ): 

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

112 accessor = getattr( attribute_, aname ) 

113 if None is accessor: continue 

114 if try_repair_closure( accessor ): return