Coverage for sources/classcore/utilities.py: 100%
60 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-11 02:31 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-11 02:31 +0000
1# vim: set filetype=python fileencoding=utf-8:
2# -*- coding: utf-8 -*-
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#============================================================================#
21''' Various utilities for class manipulation. '''
24from . import __
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 ) ) )
35def getattr0(
36 objct: object, /, name: str, default: __.typx.Any
37) -> __.typx.Any:
38 ''' Returns special private attribute from object.
40 This avoids inheritance-related collisions.
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 )
54def delattr0( objct: object, /, name: str ) -> None:
55 ''' Deletes special private attribute on object.
57 This avoids inheritance-related collisions.
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 )
73def setattr0( objct: object, /, name: str, value: __.typx.Any ) -> None:
74 ''' Assigns special private attribute to object.
76 This avoids inheritance-related collisions.
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 )
92def mangle_name( objct: object, /, name: str ) -> str:
93 ''' Mangles attribute name so that it is unique.
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}"
106def qualify_class_name( cls: type ) -> str:
107 ''' Returns fully-qualified class name. '''
108 return f"{cls.__module__}.{cls.__qualname__}"
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
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
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