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

159 statements  

« prev     ^ index     » next       coverage.py v7.9.0, created at 2025-06-12 01:35 +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 # TODO? Only use mangling if not slotted. 

53 behaviors_name_m = _utilities.mangle_name( cls, behaviors_name ) 

54 annotations[ behaviors_name_m ] = set[ str ] 

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

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

57 

58 

59def apply_cfc_dynadoc_configuration( 

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

61 attributes_namer: _nomina.AttributesNamer, 

62 configuration: _nomina.DynadocConfiguration, 

63) -> None: 

64 ''' Stores Dynadoc configuration on metaclass. ''' 

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

66 setattr( clscls, configuration_name, configuration ) 

67 

68 

69def apply_cfc_constructor( 

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

71 attributes_namer: _nomina.AttributesNamer, 

72 error_class_provider: _nomina.ErrorClassProvider, 

73) -> None: 

74 ''' Injects '__new__' method into metaclass. ''' 

75 preprocessors = ( 

76 _behaviors.produce_class_construction_preprocessor( 

77 attributes_namer = attributes_namer ), ) 

78 postprocessors = ( 

79 _behaviors.produce_class_construction_postprocessor( 

80 attributes_namer = attributes_namer, 

81 error_class_provider = error_class_provider ), ) 

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

83 _factories.produce_class_constructor( 

84 attributes_namer = attributes_namer, 

85 preprocessors = preprocessors, 

86 postprocessors = postprocessors ) ) 

87 decorator = produce_class_construction_decorator( 

88 attributes_namer = attributes_namer, constructor = constructor ) 

89 decorator( clscls ) 

90 

91 

92def apply_cfc_initializer( 

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

94) -> None: 

95 ''' Injects '__init__' method into metaclass. ''' 

96 completers = ( 

97 _behaviors.produce_class_initialization_completer( 

98 attributes_namer = attributes_namer ), ) 

99 initializer = ( 

100 _factories.produce_class_initializer( 

101 attributes_namer = attributes_namer, 

102 completers = completers ) ) 

103 decorator = produce_class_initialization_decorator( 

104 attributes_namer = attributes_namer, initializer = initializer ) 

105 decorator( clscls ) 

106 

107 

108def apply_cfc_attributes_assigner( 

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

110 attributes_namer: _nomina.AttributesNamer, 

111 error_class_provider: _nomina.ErrorClassProvider, 

112 implementation_core: _nomina.AssignerCore, 

113) -> None: 

114 ''' Injects '__setattr__' method into metaclass. ''' 

115 decorator = produce_attributes_assignment_decorator( 

116 level = 'classes', 

117 attributes_namer = attributes_namer, 

118 error_class_provider = error_class_provider, 

119 implementation_core = implementation_core ) 

120 decorator( clscls ) 

121 

122 

123def apply_cfc_attributes_deleter( 

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

125 attributes_namer: _nomina.AttributesNamer, 

126 error_class_provider: _nomina.ErrorClassProvider, 

127 implementation_core: _nomina.DeleterCore, 

128) -> None: 

129 ''' Injects '__delattr__' method into metaclass. ''' 

130 decorator = produce_attributes_deletion_decorator( 

131 level = 'classes', 

132 attributes_namer = attributes_namer, 

133 error_class_provider = error_class_provider, 

134 implementation_core = implementation_core ) 

135 decorator( clscls ) 

136 

137 

138def apply_cfc_attributes_surveyor( 

139 clscls: type[ __.T ], 

140 attributes_namer: _nomina.AttributesNamer, 

141 implementation_core: _nomina.SurveyorCore, 

142) -> None: 

143 ''' Injects '__dir__' method into metaclass. ''' 

144 decorator = produce_attributes_surveillance_decorator( 

145 level = 'classes', 

146 attributes_namer = attributes_namer, 

147 implementation_core = implementation_core ) 

148 decorator( clscls ) 

149 

150 

151def class_factory( # noqa: PLR0913 

152 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

153 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

154 assigner_core: _nomina.AssignerCore = ( 

155 _behaviors.assign_attribute_if_mutable ), 

156 deleter_core: _nomina.DeleterCore = ( 

157 _behaviors.delete_attribute_if_mutable ), 

158 surveyor_core: _nomina.SurveyorCore = ( 

159 _behaviors.survey_visible_attributes ), 

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

161 _dynadoc_configuration ), 

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

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

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

165 apply_cfc_dynadoc_configuration( 

166 clscls, 

167 attributes_namer = attributes_namer, 

168 configuration = dynadoc_configuration ) 

169 apply_cfc_constructor( 

170 clscls, 

171 attributes_namer = attributes_namer, 

172 error_class_provider = error_class_provider ) 

173 apply_cfc_initializer( clscls, attributes_namer = attributes_namer ) 

174 apply_cfc_attributes_assigner( 

175 clscls, 

176 attributes_namer = attributes_namer, 

177 error_class_provider = error_class_provider, 

178 implementation_core = assigner_core ) 

179 apply_cfc_attributes_deleter( 

180 clscls, 

181 attributes_namer = attributes_namer, 

182 error_class_provider = error_class_provider, 

183 implementation_core = deleter_core ) 

184 apply_cfc_attributes_surveyor( 

185 clscls, 

186 attributes_namer = attributes_namer, 

187 implementation_core = surveyor_core ) 

188 return clscls 

189 

190 return decorate 

191 

192 

193def produce_instances_initialization_decorator( 

194 attributes_namer: _nomina.AttributesNamer, 

195 mutables: _nomina.BehaviorExclusionVerifiersOmni, 

196 visibles: _nomina.BehaviorExclusionVerifiersOmni, 

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

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

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

200 behaviors: set[ str ] = set( ) 

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

202 _behaviors.record_behavior( 

203 cls, attributes_namer = attributes_namer, 

204 level = 'instances', basename = 'mutables', 

205 label = _nomina.immutability_label, behaviors = behaviors, 

206 verifiers = mutables ) 

207 _behaviors.record_behavior( 

208 cls, attributes_namer = attributes_namer, 

209 level = 'instances', basename = 'visibles', 

210 label = _nomina.concealment_label, behaviors = behaviors, 

211 verifiers = visibles ) 

212 original = cls.__dict__.get( '__init__' ) 

213 

214 if original is None: 

215 

216 def initialize_with_super( 

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

218 ) -> None: 

219 super( cls, self ).__init__( *posargs, **nomargs ) 

220 # Only record behaviors at start of MRO. 

221 if cls is not type( self ): return 

222 behaviors_: set[ str ] = ( 

223 _utilities.getattr0( self, behaviors_name, set( ) ) ) 

224 behaviors_.update( behaviors ) 

225 _utilities.setattr0( 

226 self, behaviors_name, frozenset( behaviors_ ) ) 

227 

228 cls.__init__ = initialize_with_super 

229 

230 else: 

231 

232 @__.funct.wraps( original ) 

233 def initialize_with_original( 

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

235 ) -> None: 

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

237 # Only record behaviors at start of MRO. 

238 if cls is not type( self ): return 

239 behaviors_: set[ str ] = ( 

240 _utilities.getattr0( self, behaviors_name, set( ) ) ) 

241 behaviors_.update( behaviors ) 

242 _utilities.setattr0( 

243 self, behaviors_name, frozenset( behaviors_ ) ) 

244 

245 cls.__init__ = initialize_with_original 

246 

247 return cls 

248 

249 return decorate 

250 

251 

252def produce_attributes_assignment_decorator( 

253 level: str, 

254 attributes_namer: _nomina.AttributesNamer, 

255 error_class_provider: _nomina.ErrorClassProvider, 

256 implementation_core: _nomina.AssignerCore, 

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

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

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

260 leveli = 'class' if level == 'classes' else level 

261 original = cls.__dict__.get( '__setattr__' ) 

262 

263 if original is None: 

264 

265 def assign_with_super( 

266 self: object, name: str, value: __.typx.Any 

267 ) -> None: 

268 ligation = super( cls, self ).__setattr__ 

269 # Only enforce behaviors at start of MRO. 

270 if cls is not type( self ): 

271 ligation( name, value ) 

272 return 

273 implementation_core( 

274 self, 

275 ligation = ligation, 

276 attributes_namer = attributes_namer, 

277 error_class_provider = error_class_provider, 

278 level = leveli, 

279 name = name, value = value ) 

280 

281 cls.__setattr__ = assign_with_super 

282 

283 else: 

284 

285 @__.funct.wraps( original ) 

286 def assign_with_original( 

287 self: object, name: str, value: __.typx.Any 

288 ) -> None: 

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

290 # Only enforce behaviors at start of MRO. 

291 if cls is not type( self ): 

292 ligation( name, value ) 

293 return 

294 implementation_core( 

295 self, 

296 ligation = ligation, 

297 attributes_namer = attributes_namer, 

298 error_class_provider = error_class_provider, 

299 level = leveli, 

300 name = name, value = value ) 

301 

302 cls.__setattr__ = assign_with_original 

303 

304 return cls 

305 

306 return decorate 

307 

308 

309def produce_attributes_deletion_decorator( 

310 level: str, 

311 attributes_namer: _nomina.AttributesNamer, 

312 error_class_provider: _nomina.ErrorClassProvider, 

313 implementation_core: _nomina.DeleterCore, 

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

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

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

317 leveli = 'class' if level == 'classes' else level 

318 original = cls.__dict__.get( '__delattr__' ) 

319 

320 if original is None: 

321 

322 def delete_with_super( self: object, name: str ) -> None: 

323 ligation = super( cls, self ).__delattr__ 

324 # Only enforce behaviors at start of MRO. 

325 if cls is not type( self ): 

326 ligation( name ) 

327 return 

328 implementation_core( 

329 self, 

330 ligation = ligation, 

331 attributes_namer = attributes_namer, 

332 error_class_provider = error_class_provider, 

333 level = leveli, 

334 name = name ) 

335 

336 cls.__delattr__ = delete_with_super 

337 

338 else: 

339 

340 @__.funct.wraps( original ) 

341 def delete_with_original( self: object, name: str ) -> None: 

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

343 # Only enforce behaviors at start of MRO. 

344 if cls is not type( self ): 

345 ligation( name ) 

346 return 

347 implementation_core( 

348 self, 

349 ligation = ligation, 

350 attributes_namer = attributes_namer, 

351 error_class_provider = error_class_provider, 

352 level = leveli, 

353 name = name ) 

354 

355 cls.__delattr__ = delete_with_original 

356 

357 return cls 

358 

359 return decorate 

360 

361 

362def produce_attributes_surveillance_decorator( 

363 level: str, 

364 attributes_namer: _nomina.AttributesNamer, 

365 implementation_core: _nomina.SurveyorCore, 

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

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

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

369 leveli = 'class' if level == 'classes' else level 

370 original = cls.__dict__.get( '__dir__' ) 

371 

372 if original is None: 

373 

374 def survey_with_super( 

375 self: object 

376 ) -> __.cabc.Iterable[ str ]: 

377 ligation = super( cls, self ).__dir__ 

378 # Only enforce behaviors at start of MRO. 

379 if cls is not type( self ): return ligation( ) 

380 return implementation_core( 

381 self, 

382 ligation = ligation, 

383 attributes_namer = attributes_namer, 

384 level = leveli ) 

385 

386 cls.__dir__ = survey_with_super 

387 

388 else: 

389 

390 @__.funct.wraps( original ) 

391 def survey_with_original( 

392 self: object 

393 ) -> __.cabc.Iterable[ str ]: 

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

395 # Only enforce behaviors at start of MRO. 

396 if cls is not type( self ): return ligation( ) 

397 return implementation_core( 

398 self, 

399 ligation = ligation, 

400 attributes_namer = attributes_namer, 

401 level = leveli ) 

402 

403 cls.__dir__ = survey_with_original 

404 

405 return cls 

406 

407 return decorate 

408 

409 

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

411def dataclass_with_standard_behaviors( # noqa: PLR0913 

412 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

413 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

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

415 assigner_core: _nomina.AssignerCore = ( 

416 _behaviors.assign_attribute_if_mutable ), 

417 deleter_core: _nomina.DeleterCore = ( 

418 _behaviors.delete_attribute_if_mutable ), 

419 surveyor_core: _nomina.SurveyorCore = ( 

420 _behaviors.survey_visible_attributes ), 

421 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default, 

422 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default, 

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

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

425 ''' Dataclass decorator factory. ''' 

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

427 _produce_instances_decorators( 

428 attributes_namer = attributes_namer, 

429 error_class_provider = error_class_provider, 

430 assigner_core = assigner_core, 

431 deleter_core = deleter_core, 

432 surveyor_core = surveyor_core, 

433 mutables = mutables, 

434 visibles = visibles ) ) 

435 preparers: _nomina.DecorationPreparers[ __.U ] = ( 

436 _produce_instances_decoration_preparers( 

437 attributes_namer = attributes_namer, 

438 error_class_provider = error_class_provider, 

439 class_preparer = prepare_dataclass_for_instances ) ) 

440 return decoration_by( 

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

442 

443 

444def with_standard_behaviors( # noqa: PLR0913 

445 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

446 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

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

448 assigner_core: _nomina.AssignerCore = ( 

449 _behaviors.assign_attribute_if_mutable ), 

450 deleter_core: _nomina.DeleterCore = ( 

451 _behaviors.delete_attribute_if_mutable ), 

452 surveyor_core: _nomina.SurveyorCore = ( 

453 _behaviors.survey_visible_attributes ), 

454 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default, 

455 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default, 

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

457 ''' Class decorator factory. ''' 

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

459 _produce_instances_decorators( 

460 attributes_namer = attributes_namer, 

461 error_class_provider = error_class_provider, 

462 assigner_core = assigner_core, 

463 deleter_core = deleter_core, 

464 surveyor_core = surveyor_core, 

465 mutables = mutables, 

466 visibles = visibles ) ) 

467 preparers: _nomina.DecorationPreparers[ __.U ] = ( 

468 _produce_instances_decoration_preparers( 

469 attributes_namer = attributes_namer, 

470 error_class_provider = error_class_provider ) ) 

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

472 

473 

474def _produce_instances_decoration_preparers( 

475 attributes_namer: _nomina.AttributesNamer, 

476 error_class_provider: _nomina.ErrorClassProvider, 

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

478) -> _nomina.DecorationPreparers[ __.U ]: 

479 ''' Produces processors for standard decorators. ''' 

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

481 if class_preparer is not None: 

482 preprocessors.append( 

483 __.funct.partial( 

484 class_preparer, attributes_namer = attributes_namer ) ) 

485 return tuple( preprocessors ) 

486 

487 

488def _produce_instances_decorators( # noqa: PLR0913 

489 attributes_namer: _nomina.AttributesNamer, 

490 error_class_provider: _nomina.ErrorClassProvider, 

491 assigner_core: _nomina.AssignerCore, 

492 deleter_core: _nomina.DeleterCore, 

493 surveyor_core: _nomina.SurveyorCore, 

494 mutables: _nomina.BehaviorExclusionVerifiersOmni, 

495 visibles: _nomina.BehaviorExclusionVerifiersOmni, 

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

497 ''' Produces standard decorators. ''' 

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

499 decorators.append( 

500 produce_instances_initialization_decorator( 

501 attributes_namer = attributes_namer, 

502 mutables = mutables, visibles = visibles ) ) 

503 if mutables != '*': 

504 decorators.append( 

505 produce_attributes_assignment_decorator( 

506 level = 'instances', 

507 attributes_namer = attributes_namer, 

508 error_class_provider = error_class_provider, 

509 implementation_core = assigner_core ) ) 

510 decorators.append( 

511 produce_attributes_deletion_decorator( 

512 level = 'instances', 

513 attributes_namer = attributes_namer, 

514 error_class_provider = error_class_provider, 

515 implementation_core = deleter_core ) ) 

516 if visibles != '*': 

517 decorators.append( 

518 produce_attributes_surveillance_decorator( 

519 level = 'instances', 

520 attributes_namer = attributes_namer, 

521 implementation_core = surveyor_core ) ) 

522 return decorators