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

45 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-05-01 22:29 +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 __future__ import annotations 

25 

26from . import __ 

27 

28 

29def describe_object( obj: object ) -> str: 

30 if __.inspect.isclass( obj ): 

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

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

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

34 

35 

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

37 ''' Returns private attribute from object. 

38 

39 Uses mangled attribute which is unique to the class. 

40 ''' 

41 name_m = mangle_name( obj, name ) 

42 return getattr( obj, name_m, default ) 

43 

44 

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

46 ''' Deletes private attribute on object. 

47 

48 Uses mangled attribute which is unique to the class. 

49 ''' 

50 name_m = mangle_name( obj, name ) 

51 delattr( obj, name_m ) 

52 

53 

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

55 ''' Assigns private attribute to object. 

56 

57 Uses mangled attribute which is unique to the class. 

58 ''' 

59 name_m = mangle_name( obj, name ) 

60 setattr( obj, name_m, value ) 

61 

62 

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

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

65 

66 Effectively provides name of private member attribute, 

67 which is unique across class inheritance. 

68 ''' 

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

70 return mangle_name( type( obj ), name ) 

71 namehash = __.hashlib.sha256( ) 

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

73 namehash_hex = namehash.hexdigest( ) 

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

75 

76 

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

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