Coverage for sources/classcore/utilities.py: 100%
60 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-02 03:53 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-02 03:53 +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 # TODO: Replace expensive SHA-256 hash with simple 'id'.
99 # Need to debug weird issue with using 'id' early on dataclasses.
100 if not __.inspect.isclass( objct ):
101 return mangle_name( type( objct ), name )
102 # return "{name}{uid}".format( name = name, uid = id( objct ) )
103 namehash = __.hashlib.sha256( )
104 namehash.update( qualify_class_name( objct ).encode( ) )
105 namehash_hex = namehash.hexdigest( )
106 return f"{name}{namehash_hex}"
109def qualify_class_name( cls: type ) -> str:
110 ''' Returns fully-qualified class name. '''
111 return f"{cls.__module__}.{cls.__qualname__}"
114def repair_class_reproduction( original: type, reproduction: type ) -> None:
115 ''' Repairs a class reproduction, if necessary. '''
116 match __.platform.python_implementation( ):
117 case 'CPython': # pragma: no branch
118 _repair_cpython_class_closures( original, reproduction )
119 case _: pass # pragma: no cover
122def _repair_cpython_class_closures(
123 original: type, reproduction: type
124) -> None:
125 # Adapted from https://github.com/python/cpython/pull/124455/files
126 def try_repair_closure(
127 function: __.cabc.Callable[ ..., __.typx.Any ]
128 ) -> bool:
129 try: index = function.__code__.co_freevars.index( '__class__' )
130 except ValueError: return False
131 if not function.__closure__: return False # pragma: no branch
132 closure = function.__closure__[ index ]
133 if original is closure.cell_contents: # pragma: no branch
134 closure.cell_contents = reproduction
135 return True
136 return False # pragma: no cover
138 for attribute in reproduction.__dict__.values( ):
139 attribute_ = __.inspect.unwrap( attribute )
140 if ( __.inspect.isfunction( attribute_ )
141 and try_repair_closure( attribute_ )
142 ): return
143 if isinstance( attribute_, property ):
144 for aname in ( 'fget', 'fset', 'fdel' ):
145 accessor = getattr( attribute_, aname )
146 if None is accessor: continue
147 if try_repair_closure( accessor ): return