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

124 statements  

« 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 -*- 

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''' Standard decorators. ''' 

22# TODO? Add attribute value transformer as standard decorator argument. 

23 

24# ruff: noqa: F401 

25 

26 

27from __future__ import annotations 

28 

29from .. import factories as _factories 

30from .. import utilities as _utilities 

31from ..decorators import ( 

32 decoration_by, 

33 produce_class_construction_decorator, 

34 produce_class_initialization_decorator, 

35) 

36from . import __ 

37from . import behaviors as _behaviors 

38from . import nomina as _nomina 

39 

40 

41_U = __.typx.TypeVar( '_U' ) 

42 

43 

44_dataclass_core = __.dcls.dataclass( kw_only = True, slots = True ) 

45 

46 

47def _produce_class_factory_core( 

48 attributes_namer: _nomina.AttributesNamer, 

49 error_class_provider: _nomina.ErrorClassProvider, 

50) -> tuple[ _nomina.ClassConstructor, _nomina.ClassInitializer ]: 

51 preprocessors = ( 

52 _behaviors.produce_class_construction_preprocessor( 

53 attributes_namer = attributes_namer ), ) 

54 postprocessors = ( 

55 _behaviors.produce_class_construction_postprocessor( 

56 attributes_namer = attributes_namer ), ) 

57 completers = ( 

58 _behaviors.produce_class_initialization_completer( 

59 attributes_namer = attributes_namer ), ) 

60 constructor = ( 

61 _factories.produce_class_constructor( 

62 attributes_namer = attributes_namer, 

63 preprocessors = preprocessors, 

64 postprocessors = postprocessors ) ) 

65 initializer = ( 

66 _factories.produce_class_initializer( 

67 attributes_namer = attributes_namer, 

68 completers = completers ) ) 

69 return constructor, initializer 

70 

71 

72def prepare_dataclass_for_instances( 

73 cls: type, 

74 decorators: _nomina.DecoratorsMutable, /, *, 

75 attributes_namer: _nomina.AttributesNamer, 

76) -> None: 

77 ''' Annotates dataclass in support of instantiation machinery. ''' 

78 annotations = __.inspect.get_annotations( cls ) 

79 behaviors_name = attributes_namer( 'instance', 'behaviors' ) 

80 behaviors_name_m = _utilities.mangle_name( cls, behaviors_name ) 

81 annotations[ behaviors_name_m ] = set[ str ] 

82 setattr( cls, '__annotations__', annotations ) # in case of absence 

83 setattr( cls, behaviors_name_m, __.dcls.field( init = False ) ) 

84 

85 

86def produce_class_factory_decorators( 

87 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

88 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

89 assigner_core: _nomina.AssignerCore = ( 

90 _behaviors.assign_attribute_if_mutable ), 

91 deleter_core: _nomina.DeleterCore = ( 

92 _behaviors.delete_attribute_if_mutable ), 

93 surveyor_core: _nomina.SurveyorCore = ( 

94 _behaviors.survey_visible_attributes ), 

95) -> _nomina.Decorators: 

96 decorators: list[ _nomina.Decorator ] = [ ] 

97 constructor, initializer = ( 

98 _produce_class_factory_core( 

99 attributes_namer = attributes_namer, 

100 error_class_provider = error_class_provider ) ) 

101 decorators.append( 

102 produce_class_construction_decorator( 

103 attributes_namer = attributes_namer, 

104 constructor = constructor ) ) 

105 decorators.append( 

106 produce_class_initialization_decorator( 

107 attributes_namer = attributes_namer, 

108 initializer = initializer ) ) 

109 decorators.append( 

110 produce_attributes_assignment_decorator( 

111 level = 'class', 

112 attributes_namer = attributes_namer, 

113 error_class_provider = error_class_provider, 

114 implementation_core = assigner_core ) ) 

115 decorators.append( 

116 produce_attributes_deletion_decorator( 

117 level = 'class', 

118 attributes_namer = attributes_namer, 

119 error_class_provider = error_class_provider, 

120 implementation_core = deleter_core ) ) 

121 decorators.append( 

122 produce_attributes_surveillance_decorator( 

123 level = 'class', 

124 attributes_namer = attributes_namer, 

125 implementation_core = surveyor_core ) ) 

126 return decorators 

127 

128 

129def produce_instances_initialization_decorator( 

130 attributes_namer: _nomina.AttributesNamer, 

131 mutables: _nomina.BehaviorExclusionVerifiersOmni, 

132 visibles: _nomina.BehaviorExclusionVerifiersOmni, 

133) -> _nomina.Decorator: 

134 def decorate( cls: type[ _U ] ) -> type[ _U ]: 

135 initializer_name = attributes_namer( 'instances', 'initializer' ) 

136 extant = getattr( cls, initializer_name, None ) 

137 original = getattr( cls, '__init__' ) 

138 if extant is original: return cls 

139 behaviors: set[ str ] = set( ) 

140 behaviors_name = attributes_namer( 'instance', 'behaviors' ) 

141 behaviors_name_m = _utilities.mangle_name( cls, behaviors_name ) 

142 _behaviors.record_behavior( 

143 cls, attributes_namer = attributes_namer, 

144 level = 'instances', basename = 'mutables', 

145 label = _behaviors.immutability_label, behaviors = behaviors, 

146 verifiers = mutables ) 

147 _behaviors.record_behavior( 

148 cls, attributes_namer = attributes_namer, 

149 level = 'instances', basename = 'visibles', 

150 label = _behaviors.concealment_label, behaviors = behaviors, 

151 verifiers = visibles ) 

152 

153 @__.funct.wraps( original ) 

154 def initialize( 

155 self: object, *posargs: __.typx.Any, **nomargs: __.typx.Any 

156 ) -> None: 

157 original( self, *posargs, **nomargs ) 

158 behaviors_: set[ str ] = getattr( self, behaviors_name_m, set( ) ) 

159 behaviors_.update( behaviors ) 

160 setattr( self, behaviors_name_m, frozenset( behaviors_ ) ) 

161 

162 setattr( cls, initializer_name, initialize ) 

163 cls.__init__ = initialize 

164 return cls 

165 

166 return decorate 

167 

168 

169def produce_attributes_assignment_decorator( 

170 level: str, 

171 attributes_namer: _nomina.AttributesNamer, 

172 error_class_provider: _nomina.ErrorClassProvider, 

173 implementation_core: _nomina.AssignerCore, 

174) -> _nomina.Decorator: 

175 def decorate( cls: type[ _U ] ) -> type[ _U ]: 

176 assigner_name = attributes_namer( level, 'assigner' ) 

177 extant = getattr( cls, assigner_name, None ) 

178 original = getattr( cls, '__setattr__' ) 

179 if extant is original: return cls 

180 

181 @__.funct.wraps( original ) 

182 def assign( self: object, name: str, value: __.typx.Any ) -> None: 

183 implementation_core( 

184 self, 

185 ligation = __.funct.partial( original, self ), 

186 attributes_namer = attributes_namer, 

187 error_class_provider = error_class_provider, 

188 level = level, 

189 name = name, value = value ) 

190 

191 setattr( cls, assigner_name, assign ) 

192 cls.__setattr__ = assign 

193 return cls 

194 

195 return decorate 

196 

197 

198def produce_attributes_deletion_decorator( 

199 level: str, 

200 attributes_namer: _nomina.AttributesNamer, 

201 error_class_provider: _nomina.ErrorClassProvider, 

202 implementation_core: _nomina.DeleterCore, 

203) -> _nomina.Decorator: 

204 def decorate( cls: type[ _U ] ) -> type[ _U ]: 

205 deleter_name = attributes_namer( level, 'deleter' ) 

206 extant = getattr( cls, deleter_name, None ) 

207 original = getattr( cls, '__delattr__' ) 

208 if extant is original: return cls 

209 

210 @__.funct.wraps( original ) 

211 def delete( self: object, name: str ) -> None: 

212 implementation_core( 

213 self, 

214 ligation = __.funct.partial( original, self ), 

215 attributes_namer = attributes_namer, 

216 error_class_provider = error_class_provider, 

217 level = level, 

218 name = name ) 

219 

220 setattr( cls, deleter_name, delete ) 

221 cls.__delattr__ = delete 

222 return cls 

223 

224 return decorate 

225 

226 

227def produce_attributes_surveillance_decorator( 

228 level: str, 

229 attributes_namer: _nomina.AttributesNamer, 

230 implementation_core: _nomina.SurveyorCore, 

231) -> _nomina.Decorator: 

232 def decorate( cls: type[ _U ] ) -> type[ _U ]: 

233 surveyor_name = attributes_namer( level, 'surveyor' ) 

234 extant = getattr( cls, surveyor_name, None ) 

235 original = getattr( cls, '__dir__' ) 

236 if extant is original: return cls 

237 

238 @__.funct.wraps( original ) 

239 def survey( self: object ) -> __.cabc.Iterable[ str ]: 

240 return implementation_core( 

241 self, 

242 ligation = __.funct.partial( original, self ), 

243 attributes_namer = attributes_namer, 

244 level = level ) 

245 

246 setattr( cls, surveyor_name, survey ) 

247 cls.__dir__ = survey 

248 return cls 

249 

250 return decorate 

251 

252 

253def produce_decorators_factory( # noqa: PLR0913 

254 level: str, 

255 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

256 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

257 assigner_core: _nomina.AssignerCore = ( 

258 _behaviors.assign_attribute_if_mutable ), 

259 deleter_core: _nomina.DeleterCore = ( 

260 _behaviors.delete_attribute_if_mutable ), 

261 surveyor_core: _nomina.SurveyorCore = ( 

262 _behaviors.survey_visible_attributes ), 

263) -> __.cabc.Callable[ 

264 [ 

265 _nomina.BehaviorExclusionVerifiersOmni, 

266 _nomina.BehaviorExclusionVerifiersOmni 

267 ], 

268 _nomina.Decorators 

269]: 

270 def produce( 

271 mutables: _nomina.BehaviorExclusionVerifiersOmni, 

272 visibles: _nomina.BehaviorExclusionVerifiersOmni, 

273 ) -> _nomina.Decorators: 

274 ''' Produces standard decorators. ''' 

275 decorators: list[ _nomina.Decorator ] = [ ] 

276 decorators.append( 

277 produce_instances_initialization_decorator( 

278 attributes_namer = attributes_namer, 

279 mutables = mutables, visibles = visibles ) ) 

280 if mutables != '*': 

281 decorators.append( 

282 produce_attributes_assignment_decorator( 

283 level = level, 

284 attributes_namer = attributes_namer, 

285 error_class_provider = error_class_provider, 

286 implementation_core = assigner_core ) ) 

287 decorators.append( 

288 produce_attributes_deletion_decorator( 

289 level = level, 

290 attributes_namer = attributes_namer, 

291 error_class_provider = error_class_provider, 

292 implementation_core = deleter_core ) ) 

293 if visibles != '*': 

294 decorators.append( 

295 produce_attributes_surveillance_decorator( 

296 level = level, 

297 attributes_namer = attributes_namer, 

298 implementation_core = surveyor_core ) ) 

299 return decorators 

300 

301 return produce 

302 

303 

304def produce_decoration_preparers_factory( 

305 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

306 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

307 class_preparer: __.typx.Optional[ _nomina.ClassPreparer ] = None, 

308) -> __.cabc.Callable[ [ ], _nomina.DecorationPreparers ]: 

309 def produce( ) -> _nomina.DecorationPreparers: 

310 ''' Produces processors for standard decorators. ''' 

311 preprocessors: list[ _nomina.DecorationPreparer ] = [ ] 

312 if class_preparer is not None: 

313 preprocessors.append( 

314 __.funct.partial( 

315 class_preparer, 

316 attributes_namer = attributes_namer ) ) 

317 return tuple( preprocessors ) 

318 

319 return produce 

320 

321 

322class_factory_decorators = produce_class_factory_decorators( ) 

323 

324 

325@__.typx.dataclass_transform( frozen_default = True, kw_only_default = True ) 

326def dataclass_with_standard_behaviors( 

327 decorators: _nomina.Decorators = ( ), 

328 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default, 

329 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default, 

330) -> _nomina.Decorator: 

331 # https://github.com/microsoft/pyright/discussions/10344 

332 ''' Dataclass decorator factory. ''' 

333 decorators_factory = produce_decorators_factory( level = 'instances' ) 

334 decorators_ = decorators_factory( mutables, visibles ) 

335 preparers_factory = produce_decoration_preparers_factory( 

336 class_preparer = prepare_dataclass_for_instances ) 

337 preparers = preparers_factory( ) 

338 return decoration_by( 

339 *decorators, _dataclass_core, *decorators_, preparers = preparers ) 

340 

341 

342def with_standard_behaviors( 

343 decorators: _nomina.Decorators = ( ), 

344 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default, 

345 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default, 

346) -> _nomina.Decorator: 

347 ''' Class decorator factory. ''' 

348 decorators_factory = produce_decorators_factory( level = 'instances' ) 

349 decorators_ = decorators_factory( mutables, visibles ) 

350 preparers_factory = produce_decoration_preparers_factory( ) 

351 preparers = preparers_factory( ) 

352 return decoration_by( *decorators, *decorators_, preparers = preparers )