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

54 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-15 20:56 +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 . import __ 

25from . import nomina as _nomina 

26from . import utilities as _utilities 

27 

28 

29def apply_decorators( 

30 cls: type[ __.U ], decorators: _nomina.Decorators[ __.U ] 

31) -> type: 

32 ''' Applies sequence of decorators to class. 

33 

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

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

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

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

38 replacement class. 

39 ''' 

40 for decorator in decorators: 

41 cls_ = decorator( cls ) 

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

43 _utilities.repair_class_reproduction( cls, cls_ ) 

44 cls = cls_ # Use the replacement class. 

45 return cls 

46 

47 

48def decoration_by( 

49 *decorators: _nomina.Decorator[ __.U ], 

50 preparers: _nomina.DecorationPreparers[ __.U ] = ( ), 

51) -> _nomina.Decorator[ __.U ]: 

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

53 

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

55 

56 Can optionally execute a sequence of decoration preparers before 

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

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

59 introspection of the class. 

60 ''' 

61 def decorate( cls: type[ __.U ] ) -> type[ __.U ]: 

62 decorators_ = list( decorators ) 

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

64 return apply_decorators( cls, decorators_ ) 

65 

66 return decorate 

67 

68 

69def produce_class_construction_decorator( 

70 attributes_namer: _nomina.AttributesNamer, 

71 constructor: _nomina.ClassConstructor[ __.T ], 

72) -> _nomina.Decorator[ __.T ]: 

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

74 

75 Decorator overrides ``__new__`` on metaclass. 

76 ''' 

77 def decorate( clscls: type[ __.T ] ) -> type[ __.T ]: 

78 original = __.typx.cast( 

79 _nomina.ClassConstructorLigation | None, 

80 clscls.__dict__.get( '__new__' ) ) # pyright: ignore 

81 

82 if original is None: 

83 

84 def construct_with_super( 

85 clscls_: type[ __.T ], 

86 name: str, 

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

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

89 decorators: _nomina.Decorators[ __.T ] = ( ), 

90 **arguments: __.typx.Any, 

91 ) -> type[ object ]: 

92 superf = __.typx.cast( 

93 _nomina.ClassConstructorLigation, 

94 super( clscls, clscls_ ).__new__ ) 

95 if clscls is not clscls_: 

96 # Parent metaclass within a hierarchy: delegate without 

97 # duplicating preprocessing or decoration. 

98 return superf( 

99 clscls_, name, bases, namespace, **arguments ) 

100 return constructor( 

101 clscls_, superf, 

102 name, bases, namespace, arguments, decorators ) 

103 

104 setattr( clscls, '__new__', construct_with_super ) 

105 

106 else: 

107 

108 def construct_with_original( 

109 clscls_: type[ __.T ], 

110 name: str, 

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

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

113 decorators: _nomina.Decorators[ __.T ] = ( ), 

114 **arguments: __.typx.Any, 

115 ) -> type[ object ]: 

116 if clscls is not clscls_: 

117 # Parent metaclass within a hierarchy: delegate without 

118 # duplicating preprocessing or decoration. 

119 return original( 

120 clscls_, name, bases, namespace, **arguments ) 

121 return constructor( 

122 clscls_, original, 

123 name, bases, namespace, arguments, decorators ) 

124 

125 setattr( clscls, '__new__', construct_with_original ) 

126 

127 return clscls 

128 

129 return decorate 

130 

131 

132def produce_class_initialization_decorator( 

133 attributes_namer: _nomina.AttributesNamer, 

134 initializer: _nomina.ClassInitializer, 

135) -> _nomina.Decorator[ __.T ]: 

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

137 

138 Decorator overrides ``__init__`` on metaclass. 

139 ''' 

140 def decorate( clscls: type[ __.T ] ) -> type[ __.T ]: 

141 original = __.typx.cast( 

142 _nomina.InitializerLigation | None, 

143 clscls.__dict__.get( '__init__' ) ) # pyright: ignore 

144 

145 if original is None: 

146 

147 def initialize_with_super( 

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

149 ) -> None: 

150 ligation = super( clscls, cls ).__init__ 

151 if clscls is not type( cls ): 

152 # Parent metaclass within a hierarchy: delegate without 

153 # duplicating completion logic. 

154 ligation( *posargs, **nomargs ) 

155 return 

156 initializer( cls, ligation, posargs, nomargs ) 

157 

158 clscls.__init__ = initialize_with_super 

159 

160 else: 

161 

162 @__.funct.wraps( original ) 

163 def initialize_with_original( 

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

165 ) -> None: 

166 if clscls is not type( cls ): 

167 # Parent metaclass within a hierarchy: delegate without 

168 # duplicating completion logic. 

169 original( cls, *posargs, **nomargs ) 

170 return 

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

172 initializer( cls, ligation, posargs, nomargs ) 

173 

174 clscls.__init__ = initialize_with_original 

175 

176 return clscls 

177 

178 return decorate