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

44 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''' Utilities for the decoration of classes, including metaclasses. ''' 

22 

23 

24from __future__ import annotations 

25 

26from . import __ 

27from . import nomina as _nomina 

28from . import utilities as _utilities 

29 

30 

31_T = __.typx.TypeVar( '_T', bound = type ) 

32 

33 

34def apply_decorators( cls: type, decorators: _nomina.Decorators ) -> type: 

35 ''' Applies sequence of decorators to class. 

36 

37 If decorators replace classes (e.g., ``dataclass( slots = True )``), 

38 then any necessary repairs are performed on the replacement class with 

39 respect to the original. E.g., on CPython, the class closure cell is 

40 repaired so that ``super`` operates correctly in methods of the 

41 replacement class. 

42 ''' 

43 for decorator in decorators: 

44 cls_ = decorator( cls ) 

45 if cls is cls_: continue # Simple mutation. No replacement. 

46 _utilities.repair_class_reproduction( cls, cls_ ) 

47 cls = cls_ # Use the replacement class. 

48 return cls 

49 

50 

51def decoration_by( 

52 *decorators: _nomina.Decorator, 

53 preparers: _nomina.DecorationPreparers = ( ), 

54) -> _nomina.Decorator: 

55 ''' Class decorator which applies other class decorators. 

56 

57 Useful to apply a stack of decorators as a sequence. 

58 

59 Can optionally execute a sequence of decoration preparers before 

60 applying the decorators proper. These can be used to alter the 

61 decorators list itself, such as to inject decorators based on 

62 introspection of the class. 

63 ''' 

64 def decorate( cls: type ) -> type: 

65 decorators_ = list( decorators ) 

66 for preparer in preparers: preparer( cls, decorators_ ) 

67 return apply_decorators( cls, decorators_ ) 

68 

69 return decorate 

70 

71 

72def produce_class_construction_decorator( 

73 attributes_namer: _nomina.AttributesNamer, 

74 constructor: _nomina.ClassConstructor, 

75) -> _nomina.Decorator: 

76 ''' Produces metaclass decorator to control class construction. 

77 

78 Decorator overrides ``__new__`` on metaclass. 

79 ''' 

80 def decorate( clscls: type[ _T ] ) -> type[ _T ]: 

81 constructor_name = attributes_namer( 'classes', 'constructor' ) 

82 extant = getattr( clscls, constructor_name, None ) 

83 original = getattr( clscls, '__new__' ) 

84 if extant is original: return clscls 

85 

86 def construct( 

87 clscls_: type[ _T ], 

88 name: str, 

89 bases: tuple[ type, ... ], 

90 namespace: dict[ str, __.typx.Any ], *, 

91 decorators: _nomina.Decorators = ( ), 

92 **arguments: __.typx.Any, 

93 ) -> type[ object ]: 

94 return constructor( 

95 clscls_, original, 

96 name, bases, namespace, arguments, decorators ) 

97 

98 setattr( clscls, constructor_name, construct ) 

99 setattr( clscls, '__new__', construct ) 

100 return clscls 

101 

102 return decorate 

103 

104 

105def produce_class_initialization_decorator( 

106 attributes_namer: _nomina.AttributesNamer, 

107 initializer: _nomina.ClassInitializer, 

108) -> _nomina.Decorator: 

109 ''' Produces metaclass decorator to control class initialization. 

110 

111 Decorator overrides ``__init__`` on metaclass. 

112 ''' 

113 def decorate( clscls: type[ _T ] ) -> type[ _T ]: 

114 initializer_name = attributes_namer( 'classes', 'initializer' ) 

115 extant = getattr( clscls, initializer_name, None ) 

116 original = getattr( clscls, '__init__' ) 

117 if extant is original: return clscls 

118 

119 @__.funct.wraps( original ) 

120 def initialize( 

121 cls: type, *posargs: __.typx.Any, **nomargs: __.typx.Any 

122 ) -> None: 

123 ligation = __.funct.partial( original, cls ) 

124 initializer( cls, ligation, posargs, nomargs ) 

125 

126 setattr( clscls, initializer_name, initialize ) 

127 clscls.__init__ = initialize 

128 return clscls 

129 

130 return decorate