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

35 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-29 22:58 +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 attribute from object without inheritance. ''' 

38 # Inspect object dictionary directly to suppress getattr inheritance. 

39 attrsdict = getattr( obj, '__dict__', { } ) 

40 if name in attrsdict: return attrsdict[ name ] 

41 slots = getattr( obj, '__slots__', ( ) ) 

42 # Name may be in slots but not yet assigned. 

43 if name in slots: return getattr( obj, name, default ) 

44 return default 

45 

46 

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

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

49 

50 

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

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

53 match __.platform.python_implementation( ): 

54 case 'CPython': # pragma: no branch 

55 _repair_cpython_class_closures( original, reproduction ) 

56 case _: pass # pragma: no cover 

57 

58 

59def _repair_cpython_class_closures( 

60 original: type, reproduction: type 

61) -> None: 

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

63 def try_repair_closure( 

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

65 ) -> bool: 

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

67 except ValueError: return False 

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

69 closure = function.__closure__[ index ] 

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

71 closure.cell_contents = reproduction 

72 return True 

73 return False # pragma: no cover 

74 

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

76 attribute_ = __.inspect.unwrap( attribute ) 

77 if ( __.inspect.isfunction( attribute_ ) 

78 and try_repair_closure( attribute_ ) 

79 ): return 

80 if isinstance( attribute_, property ): 

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

82 accessor = getattr( attribute_, aname ) 

83 if None is accessor: continue 

84 if try_repair_closure( accessor ): return