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

140 statements  

« prev     ^ index     » next       coverage.py v7.8.2, created at 2025-06-05 22:28 +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 .. import factories as _factories 

28from .. import utilities as _utilities 

29from ..decorators import ( 

30 decoration_by, 

31 produce_class_construction_decorator, 

32 produce_class_initialization_decorator, 

33) 

34from . import __ 

35from . import behaviors as _behaviors 

36from . import dynadoc as _dynadoc 

37from . import nomina as _nomina 

38 

39 

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

41_dynadoc_configuration = _dynadoc.produce_dynadoc_configuration( ) 

42 

43 

44def prepare_dataclass_for_instances( 

45 cls: type, 

46 decorators: _nomina.DecoratorsMutable[ __.U ], /, *, 

47 attributes_namer: _nomina.AttributesNamer, 

48) -> None: 

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

50 annotations = __.inspect.get_annotations( cls ) 

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

52 behaviors_name_m = _utilities.mangle_name( cls, behaviors_name ) 

53 annotations[ behaviors_name_m ] = set[ str ] 

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

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

56 

57 

58def apply_cfc_dynadoc_configuration( 

59 clscls: type[ __.T ], /, 

60 attributes_namer: _nomina.AttributesNamer, 

61 configuration: _nomina.DynadocConfiguration, 

62) -> None: 

63 ''' Stores Dynadoc configuration on metaclass. ''' 

64 configuration_name = attributes_namer( 'classes', 'dynadoc_configuration' ) 

65 setattr( clscls, configuration_name, configuration ) 

66 

67 

68def apply_cfc_constructor( 

69 clscls: type[ __.T ], /, attributes_namer: _nomina.AttributesNamer 

70) -> None: 

71 ''' Injects '__new__' method into metaclass. ''' 

72 preprocessors = ( 

73 _behaviors.produce_class_construction_preprocessor( 

74 attributes_namer = attributes_namer ), ) 

75 postprocessors = ( 

76 _behaviors.produce_class_construction_postprocessor( 

77 attributes_namer = attributes_namer ), ) 

78 constructor: _nomina.ClassConstructor[ __.T ] = ( 

79 _factories.produce_class_constructor( 

80 attributes_namer = attributes_namer, 

81 preprocessors = preprocessors, 

82 postprocessors = postprocessors ) ) 

83 decorator = produce_class_construction_decorator( 

84 attributes_namer = attributes_namer, constructor = constructor ) 

85 decorator( clscls ) 

86 

87 

88def apply_cfc_initializer( 

89 clscls: type[ __.T ], attributes_namer: _nomina.AttributesNamer 

90) -> None: 

91 ''' Injects '__init__' method into metaclass. ''' 

92 completers = ( 

93 _behaviors.produce_class_initialization_completer( 

94 attributes_namer = attributes_namer ), ) 

95 initializer = ( 

96 _factories.produce_class_initializer( 

97 attributes_namer = attributes_namer, 

98 completers = completers ) ) 

99 decorator = produce_class_initialization_decorator( 

100 attributes_namer = attributes_namer, initializer = initializer ) 

101 decorator( clscls ) 

102 

103 

104def apply_cfc_attributes_assigner( 

105 clscls: type[ __.T ], /, 

106 attributes_namer: _nomina.AttributesNamer, 

107 error_class_provider: _nomina.ErrorClassProvider, 

108 implementation_core: _nomina.AssignerCore, 

109) -> None: 

110 ''' Injects '__setattr__' method into metaclass. ''' 

111 decorator = produce_attributes_assignment_decorator( 

112 level = 'class', 

113 attributes_namer = attributes_namer, 

114 error_class_provider = error_class_provider, 

115 implementation_core = implementation_core ) 

116 decorator( clscls ) 

117 

118 

119def apply_cfc_attributes_deleter( 

120 clscls: type[ __.T ], /, 

121 attributes_namer: _nomina.AttributesNamer, 

122 error_class_provider: _nomina.ErrorClassProvider, 

123 implementation_core: _nomina.DeleterCore, 

124) -> None: 

125 ''' Injects '__delattr__' method into metaclass. ''' 

126 decorator = produce_attributes_deletion_decorator( 

127 level = 'class', 

128 attributes_namer = attributes_namer, 

129 error_class_provider = error_class_provider, 

130 implementation_core = implementation_core ) 

131 decorator( clscls ) 

132 

133 

134def apply_cfc_attributes_surveyor( 

135 clscls: type[ __.T ], 

136 attributes_namer: _nomina.AttributesNamer, 

137 implementation_core: _nomina.SurveyorCore, 

138) -> None: 

139 ''' Injects '__dir__' method into metaclass. ''' 

140 decorator = produce_attributes_surveillance_decorator( 

141 level = 'class', 

142 attributes_namer = attributes_namer, 

143 implementation_core = implementation_core ) 

144 decorator( clscls ) 

145 

146 

147def class_factory( # noqa: PLR0913 

148 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

149 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

150 assigner_core: _nomina.AssignerCore = ( 

151 _behaviors.assign_attribute_if_mutable ), 

152 deleter_core: _nomina.DeleterCore = ( 

153 _behaviors.delete_attribute_if_mutable ), 

154 surveyor_core: _nomina.SurveyorCore = ( 

155 _behaviors.survey_visible_attributes ), 

156 dynadoc_configuration: __.cabc.Mapping[ str, __.typx.Any ] = ( 

157 _dynadoc_configuration ), 

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

159 ''' Produces decorator to apply standard behaviors to metaclass. ''' 

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

161 apply_cfc_dynadoc_configuration( 

162 clscls, 

163 attributes_namer = attributes_namer, 

164 configuration = dynadoc_configuration ) 

165 apply_cfc_constructor( clscls, attributes_namer = attributes_namer ) 

166 apply_cfc_initializer( clscls, attributes_namer = attributes_namer ) 

167 apply_cfc_attributes_assigner( 

168 clscls, 

169 attributes_namer = attributes_namer, 

170 error_class_provider = error_class_provider, 

171 implementation_core = assigner_core ) 

172 apply_cfc_attributes_deleter( 

173 clscls, 

174 attributes_namer = attributes_namer, 

175 error_class_provider = error_class_provider, 

176 implementation_core = deleter_core ) 

177 apply_cfc_attributes_surveyor( 

178 clscls, 

179 attributes_namer = attributes_namer, 

180 implementation_core = surveyor_core ) 

181 return clscls 

182 

183 return decorate 

184 

185 

186def produce_instances_initialization_decorator( 

187 attributes_namer: _nomina.AttributesNamer, 

188 mutables: _nomina.BehaviorExclusionVerifiersOmni, 

189 visibles: _nomina.BehaviorExclusionVerifiersOmni, 

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

191 ''' Produces decorator to inject '__init__' method into class. ''' 

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

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

194 extant = getattr( cls, initializer_name, None ) 

195 original = getattr( cls, '__init__' ) 

196 if extant is original: return cls 

197 behaviors: set[ str ] = set( ) 

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

199 behaviors_name_m = _utilities.mangle_name( cls, behaviors_name ) 

200 _behaviors.record_behavior( 

201 cls, attributes_namer = attributes_namer, 

202 level = 'instances', basename = 'mutables', 

203 label = _nomina.immutability_label, behaviors = behaviors, 

204 verifiers = mutables ) 

205 _behaviors.record_behavior( 

206 cls, attributes_namer = attributes_namer, 

207 level = 'instances', basename = 'visibles', 

208 label = _nomina.concealment_label, behaviors = behaviors, 

209 verifiers = visibles ) 

210 

211 @__.funct.wraps( original ) 

212 def initialize( 

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

214 ) -> None: 

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

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

217 behaviors_.update( behaviors ) 

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

219 

220 setattr( cls, initializer_name, initialize ) 

221 cls.__init__ = initialize 

222 return cls 

223 

224 return decorate 

225 

226 

227def produce_attributes_assignment_decorator( 

228 level: str, 

229 attributes_namer: _nomina.AttributesNamer, 

230 error_class_provider: _nomina.ErrorClassProvider, 

231 implementation_core: _nomina.AssignerCore, 

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

233 ''' Produces decorator to inject '__setattr__' method into class. ''' 

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

235 assigner_name = attributes_namer( level, 'assigner' ) 

236 extant = getattr( cls, assigner_name, None ) 

237 original = getattr( cls, '__setattr__' ) 

238 if extant is original: return cls 

239 

240 @__.funct.wraps( original ) 

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

242 implementation_core( 

243 self, 

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

245 attributes_namer = attributes_namer, 

246 error_class_provider = error_class_provider, 

247 level = level, 

248 name = name, value = value ) 

249 

250 setattr( cls, assigner_name, assign ) 

251 cls.__setattr__ = assign 

252 return cls 

253 

254 return decorate 

255 

256 

257def produce_attributes_deletion_decorator( 

258 level: str, 

259 attributes_namer: _nomina.AttributesNamer, 

260 error_class_provider: _nomina.ErrorClassProvider, 

261 implementation_core: _nomina.DeleterCore, 

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

263 ''' Produces decorator to inject '__delattr__' method into class. ''' 

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

265 deleter_name = attributes_namer( level, 'deleter' ) 

266 extant = getattr( cls, deleter_name, None ) 

267 original = getattr( cls, '__delattr__' ) 

268 if extant is original: return cls 

269 

270 @__.funct.wraps( original ) 

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

272 implementation_core( 

273 self, 

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

275 attributes_namer = attributes_namer, 

276 error_class_provider = error_class_provider, 

277 level = level, 

278 name = name ) 

279 

280 setattr( cls, deleter_name, delete ) 

281 cls.__delattr__ = delete 

282 return cls 

283 

284 return decorate 

285 

286 

287def produce_attributes_surveillance_decorator( 

288 level: str, 

289 attributes_namer: _nomina.AttributesNamer, 

290 implementation_core: _nomina.SurveyorCore, 

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

292 ''' Produces decorator to inject '__dir__' method into class. ''' 

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

294 surveyor_name = attributes_namer( level, 'surveyor' ) 

295 extant = getattr( cls, surveyor_name, None ) 

296 original = getattr( cls, '__dir__' ) 

297 if extant is original: return cls 

298 

299 @__.funct.wraps( original ) 

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

301 return implementation_core( 

302 self, 

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

304 attributes_namer = attributes_namer, 

305 level = level ) 

306 

307 setattr( cls, surveyor_name, survey ) 

308 cls.__dir__ = survey 

309 return cls 

310 

311 return decorate 

312 

313 

314def produce_decorators_factory( # noqa: PLR0913 

315 level: str, 

316 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

317 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

318 assigner_core: _nomina.AssignerCore = ( 

319 _behaviors.assign_attribute_if_mutable ), 

320 deleter_core: _nomina.DeleterCore = ( 

321 _behaviors.delete_attribute_if_mutable ), 

322 surveyor_core: _nomina.SurveyorCore = ( 

323 _behaviors.survey_visible_attributes ), 

324) -> __.cabc.Callable[ 

325 [ 

326 _nomina.BehaviorExclusionVerifiersOmni, 

327 _nomina.BehaviorExclusionVerifiersOmni 

328 ], 

329 _nomina.Decorators[ __.U ] 

330]: 

331 ''' Produces decorators to imbue class with standard behaviors. ''' 

332 def produce( 

333 mutables: _nomina.BehaviorExclusionVerifiersOmni, 

334 visibles: _nomina.BehaviorExclusionVerifiersOmni, 

335 ) -> _nomina.Decorators[ __.U ]: 

336 ''' Produces standard decorators. ''' 

337 decorators: list[ _nomina.Decorator[ __.U ] ] = [ ] 

338 decorators.append( 

339 produce_instances_initialization_decorator( 

340 attributes_namer = attributes_namer, 

341 mutables = mutables, visibles = visibles ) ) 

342 if mutables != '*': 

343 decorators.append( 

344 produce_attributes_assignment_decorator( 

345 level = level, 

346 attributes_namer = attributes_namer, 

347 error_class_provider = error_class_provider, 

348 implementation_core = assigner_core ) ) 

349 decorators.append( 

350 produce_attributes_deletion_decorator( 

351 level = level, 

352 attributes_namer = attributes_namer, 

353 error_class_provider = error_class_provider, 

354 implementation_core = deleter_core ) ) 

355 if visibles != '*': 

356 decorators.append( 

357 produce_attributes_surveillance_decorator( 

358 level = level, 

359 attributes_namer = attributes_namer, 

360 implementation_core = surveyor_core ) ) 

361 return decorators 

362 

363 return produce 

364 

365 

366def produce_decoration_preparers_factory( 

367 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

368 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

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

370) -> _nomina.DecorationPreparersFactory[ __.U ]: 

371 ''' Produces factory to produce class decoration preparers. 

372 

373 E.g., a preparer needs to inject special annotations to ensure 

374 compatibility with standard behaviors before 

375 :py:func:`dataclasses.dataclass` decorates a class. 

376 ''' 

377 def produce( ) -> _nomina.DecorationPreparers[ __.U ]: 

378 ''' Produces processors for standard decorators. ''' 

379 preprocessors: list[ _nomina.DecorationPreparer[ __.U ] ] = [ ] 

380 if class_preparer is not None: 

381 preprocessors.append( 

382 __.funct.partial( 

383 class_preparer, 

384 attributes_namer = attributes_namer ) ) 

385 return tuple( preprocessors ) 

386 

387 return produce 

388 

389 

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

391def dataclass_with_standard_behaviors( 

392 decorators: _nomina.Decorators[ __.U ] = ( ), 

393 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default, 

394 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default, 

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

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

397 ''' Dataclass decorator factory. ''' 

398 decorators_factory = produce_decorators_factory( level = 'instances' ) 

399 decorators_: _nomina.Decorators[ __.U ] = ( 

400 decorators_factory( mutables, visibles ) ) 

401 preparers_factory = produce_decoration_preparers_factory( 

402 class_preparer = prepare_dataclass_for_instances ) 

403 preparers: _nomina.DecorationPreparers[ __.U ] = preparers_factory( ) 

404 return decoration_by( 

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

406 

407 

408def with_standard_behaviors( 

409 decorators: _nomina.Decorators[ __.U ] = ( ), 

410 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default, 

411 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default, 

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

413 ''' Class decorator factory. ''' 

414 decorators_factory = produce_decorators_factory( level = 'instances' ) 

415 decorators_: _nomina.Decorators[ __.U ] = ( 

416 decorators_factory( mutables, visibles ) ) 

417 preparers_factory = produce_decoration_preparers_factory( ) 

418 preparers: _nomina.DecorationPreparers[ __.U ] = preparers_factory( ) 

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