Coverage for sources/classcore/utilities.py: 100%
45 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-01 21:58 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-05-01 21:58 +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 __future__ import annotations
26from . import __
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 ) ) )
36def getattr0( obj: object, name: str, default: __.typx.Any ) -> __.typx.Any:
37 ''' Returns private attribute from object.
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 )
45def delattr0( obj: object, name: str ) -> None:
46 ''' Deletes private attribute on object.
48 Uses mangled attribute which is unique to the class.
49 '''
50 name_m = mangle_name( obj, name )
51 delattr( obj, name_m )
54def setattr0( obj: object, name: str, value: __.typx.Any ) -> None:
55 ''' Assigns private attribute to object.
57 Uses mangled attribute which is unique to the class.
58 '''
59 name_m = mangle_name( obj, name )
60 setattr( obj, name_m, value )
63def mangle_name( obj: object, name: str ) -> str:
64 ''' Mangles attribute name so that it is unique.
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}"
77def qualify_class_name( cls: type ) -> str:
78 return f"{cls.__module__}.{cls.__qualname__}"
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
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
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