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

166 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-06-25 04:55 +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 

25from .. import factories as _factories 

26from .. import utilities as _utilities 

27from ..decorators import ( 

28 decoration_by, 

29 produce_class_construction_decorator, 

30 produce_class_initialization_decorator, 

31) 

32from . import __ 

33from . import behaviors as _behaviors 

34from . import dynadoc as _dynadoc 

35from . import nomina as _nomina 

36 

37 

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

39_dynadoc_configuration = _dynadoc.produce_dynadoc_configuration( ) 

40 

41 

42def prepare_dataclass_for_instances( 

43 cls: type, 

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

45 attributes_namer: _nomina.AttributesNamer, 

46) -> None: 

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

48 annotations = __.inspect.get_annotations( cls ) 

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

50 # TODO? Only use mangling if not slotted. 

51 behaviors_name_m = _utilities.mangle_name( cls, behaviors_name ) 

52 annotations[ behaviors_name_m ] = set[ str ] 

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

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

55 

56 

57def apply_cfc_dynadoc_configuration( 

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

59 attributes_namer: _nomina.AttributesNamer, 

60 configuration: _nomina.DynadocConfiguration, 

61) -> None: 

62 ''' Stores Dynadoc configuration on metaclass. ''' 

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

64 setattr( clscls, configuration_name, configuration ) 

65 

66 

67def apply_cfc_constructor( 

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

69 attributes_namer: _nomina.AttributesNamer, 

70 error_class_provider: _nomina.ErrorClassProvider, 

71) -> None: 

72 ''' Injects '__new__' method into metaclass. ''' 

73 preprocessors = ( 

74 _behaviors.produce_class_construction_preprocessor( 

75 attributes_namer = attributes_namer ), ) 

76 postprocessors = ( 

77 _behaviors.produce_class_construction_postprocessor( 

78 attributes_namer = attributes_namer, 

79 error_class_provider = error_class_provider ), ) 

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

81 _factories.produce_class_constructor( 

82 attributes_namer = attributes_namer, 

83 preprocessors = preprocessors, 

84 postprocessors = postprocessors ) ) 

85 decorator = produce_class_construction_decorator( 

86 attributes_namer = attributes_namer, constructor = constructor ) 

87 decorator( clscls ) 

88 

89 

90def apply_cfc_initializer( 

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

92) -> None: 

93 ''' Injects '__init__' method into metaclass. ''' 

94 completers = ( 

95 _behaviors.produce_class_initialization_completer( 

96 attributes_namer = attributes_namer ), ) 

97 initializer = ( 

98 _factories.produce_class_initializer( 

99 attributes_namer = attributes_namer, 

100 completers = completers ) ) 

101 decorator = produce_class_initialization_decorator( 

102 attributes_namer = attributes_namer, initializer = initializer ) 

103 decorator( clscls ) 

104 

105 

106def apply_cfc_attributes_assigner( 

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

108 attributes_namer: _nomina.AttributesNamer, 

109 error_class_provider: _nomina.ErrorClassProvider, 

110 implementation_core: _nomina.AssignerCore, 

111) -> None: 

112 ''' Injects '__setattr__' method into metaclass. ''' 

113 decorator = produce_attributes_assignment_decorator( 

114 level = 'classes', 

115 attributes_namer = attributes_namer, 

116 error_class_provider = error_class_provider, 

117 implementation_core = implementation_core ) 

118 decorator( clscls ) 

119 

120 

121def apply_cfc_attributes_deleter( 

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

123 attributes_namer: _nomina.AttributesNamer, 

124 error_class_provider: _nomina.ErrorClassProvider, 

125 implementation_core: _nomina.DeleterCore, 

126) -> None: 

127 ''' Injects '__delattr__' method into metaclass. ''' 

128 decorator = produce_attributes_deletion_decorator( 

129 level = 'classes', 

130 attributes_namer = attributes_namer, 

131 error_class_provider = error_class_provider, 

132 implementation_core = implementation_core ) 

133 decorator( clscls ) 

134 

135 

136def apply_cfc_attributes_surveyor( 

137 clscls: type[ __.T ], 

138 attributes_namer: _nomina.AttributesNamer, 

139 implementation_core: _nomina.SurveyorCore, 

140) -> None: 

141 ''' Injects '__dir__' method into metaclass. ''' 

142 decorator = produce_attributes_surveillance_decorator( 

143 level = 'classes', 

144 attributes_namer = attributes_namer, 

145 implementation_core = implementation_core ) 

146 decorator( clscls ) 

147 

148 

149def class_factory( # noqa: PLR0913 

150 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

151 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

152 assigner_core: _nomina.AssignerCore = ( 

153 _behaviors.assign_attribute_if_mutable ), 

154 deleter_core: _nomina.DeleterCore = ( 

155 _behaviors.delete_attribute_if_mutable ), 

156 surveyor_core: _nomina.SurveyorCore = ( 

157 _behaviors.survey_visible_attributes ), 

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

159 _dynadoc_configuration ), 

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

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

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

163 apply_cfc_dynadoc_configuration( 

164 clscls, 

165 attributes_namer = attributes_namer, 

166 configuration = dynadoc_configuration ) 

167 apply_cfc_constructor( 

168 clscls, 

169 attributes_namer = attributes_namer, 

170 error_class_provider = error_class_provider ) 

171 apply_cfc_initializer( clscls, attributes_namer = attributes_namer ) 

172 apply_cfc_attributes_assigner( 

173 clscls, 

174 attributes_namer = attributes_namer, 

175 error_class_provider = error_class_provider, 

176 implementation_core = assigner_core ) 

177 apply_cfc_attributes_deleter( 

178 clscls, 

179 attributes_namer = attributes_namer, 

180 error_class_provider = error_class_provider, 

181 implementation_core = deleter_core ) 

182 apply_cfc_attributes_surveyor( 

183 clscls, 

184 attributes_namer = attributes_namer, 

185 implementation_core = surveyor_core ) 

186 return clscls 

187 

188 return decorate 

189 

190 

191def produce_instances_initialization_decorator( # noqa: PLR0913 

192 attributes_namer: _nomina.AttributesNamer, 

193 assigner_core: __.typx.Optional[ _nomina.AssignerCore ], 

194 deleter_core: __.typx.Optional[ _nomina.DeleterCore ], 

195 surveyor_core: __.typx.Optional[ _nomina.SurveyorCore ], 

196 mutables: _nomina.BehaviorExclusionVerifiersOmni, 

197 visibles: _nomina.BehaviorExclusionVerifiersOmni, 

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

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

200 cores = dict( 

201 instances_assigner_core = assigner_core, 

202 instances_deleter_core = deleter_core, 

203 instances_surveyor_core = surveyor_core ) 

204 cores_default = dict( 

205 assigner = _behaviors.assign_attribute_if_mutable, 

206 deleter = _behaviors.delete_attribute_if_mutable, 

207 surveyor = _behaviors.survey_visible_attributes ) 

208 

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

210 for core_name in ( 'assigner', 'deleter', 'surveyor' ): 

211 core_function = _behaviors.access_core_function( 

212 cls, 

213 attributes_namer = attributes_namer, 

214 arguments = cores, 

215 level = 'instances', name = core_name, 

216 default = cores_default[ core_name ] ) 

217 core_aname = attributes_namer( 'instances', f"{core_name}_core" ) 

218 setattr( cls, core_aname, core_function ) 

219 behaviors: set[ str ] = set( ) 

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

221 _behaviors.record_behavior( 

222 cls, attributes_namer = attributes_namer, 

223 level = 'instances', basename = 'mutables', 

224 label = _nomina.immutability_label, behaviors = behaviors, 

225 verifiers = mutables ) 

226 _behaviors.record_behavior( 

227 cls, attributes_namer = attributes_namer, 

228 level = 'instances', basename = 'visibles', 

229 label = _nomina.concealment_label, behaviors = behaviors, 

230 verifiers = visibles ) 

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

232 

233 if original is None: 

234 

235 def initialize_with_super( 

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

237 ) -> None: 

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

239 # Only record behaviors at start of MRO. 

240 if cls is not type( self ): return 

241 behaviors_: set[ str ] = ( 

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

243 behaviors_.update( behaviors ) 

244 _utilities.setattr0( 

245 self, behaviors_name, frozenset( behaviors_ ) ) 

246 

247 cls.__init__ = initialize_with_super 

248 

249 else: 

250 

251 @__.funct.wraps( original ) 

252 def initialize_with_original( 

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

254 ) -> None: 

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

256 # Only record behaviors at start of MRO. 

257 if cls is not type( self ): return 

258 behaviors_: set[ str ] = ( 

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

260 behaviors_.update( behaviors ) 

261 _utilities.setattr0( 

262 self, behaviors_name, frozenset( behaviors_ ) ) 

263 

264 cls.__init__ = initialize_with_original 

265 

266 return cls 

267 

268 return decorate 

269 

270 

271def produce_attributes_assignment_decorator( 

272 level: str, 

273 attributes_namer: _nomina.AttributesNamer, 

274 error_class_provider: _nomina.ErrorClassProvider, 

275 implementation_core: __.typx.Optional[ _nomina.AssignerCore ], 

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

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

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

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

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

281 core = _behaviors.access_core_function( 

282 cls, 

283 attributes_namer = attributes_namer, 

284 arguments = { f"{leveli}_assigner": implementation_core }, 

285 level = leveli, name = 'assigner', 

286 default = _behaviors.assign_attribute_if_mutable ) 

287 

288 if original is None: 

289 

290 def assign_with_super( 

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

292 ) -> None: 

293 ligation = super( cls, self ).__setattr__ 

294 # Only enforce behaviors at start of MRO. 

295 if cls is not type( self ): 

296 ligation( name, value ) 

297 return 

298 core( 

299 self, 

300 ligation = ligation, 

301 attributes_namer = attributes_namer, 

302 error_class_provider = error_class_provider, 

303 level = leveli, 

304 name = name, value = value ) 

305 

306 cls.__setattr__ = assign_with_super 

307 

308 else: 

309 

310 @__.funct.wraps( original ) 

311 def assign_with_original( 

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

313 ) -> None: 

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

315 # Only enforce behaviors at start of MRO. 

316 if cls is not type( self ): 

317 ligation( name, value ) 

318 return 

319 core( 

320 self, 

321 ligation = ligation, 

322 attributes_namer = attributes_namer, 

323 error_class_provider = error_class_provider, 

324 level = leveli, 

325 name = name, value = value ) 

326 

327 cls.__setattr__ = assign_with_original 

328 

329 return cls 

330 

331 return decorate 

332 

333 

334def produce_attributes_deletion_decorator( 

335 level: str, 

336 attributes_namer: _nomina.AttributesNamer, 

337 error_class_provider: _nomina.ErrorClassProvider, 

338 implementation_core: __.typx.Optional[ _nomina.DeleterCore ], 

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

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

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

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

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

344 core = _behaviors.access_core_function( 

345 cls, 

346 attributes_namer = attributes_namer, 

347 arguments = { f"{leveli}_deleter": implementation_core }, 

348 level = leveli, name = 'deleter', 

349 default = _behaviors.delete_attribute_if_mutable ) 

350 

351 if original is None: 

352 

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

354 ligation = super( cls, self ).__delattr__ 

355 # Only enforce behaviors at start of MRO. 

356 if cls is not type( self ): 

357 ligation( name ) 

358 return 

359 core( 

360 self, 

361 ligation = ligation, 

362 attributes_namer = attributes_namer, 

363 error_class_provider = error_class_provider, 

364 level = leveli, 

365 name = name ) 

366 

367 cls.__delattr__ = delete_with_super 

368 

369 else: 

370 

371 @__.funct.wraps( original ) 

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

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

374 # Only enforce behaviors at start of MRO. 

375 if cls is not type( self ): 

376 ligation( name ) 

377 return 

378 core( 

379 self, 

380 ligation = ligation, 

381 attributes_namer = attributes_namer, 

382 error_class_provider = error_class_provider, 

383 level = leveli, 

384 name = name ) 

385 

386 cls.__delattr__ = delete_with_original 

387 

388 return cls 

389 

390 return decorate 

391 

392 

393def produce_attributes_surveillance_decorator( 

394 level: str, 

395 attributes_namer: _nomina.AttributesNamer, 

396 implementation_core: __.typx.Optional[ _nomina.SurveyorCore ], 

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

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

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

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

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

402 core = _behaviors.access_core_function( 

403 cls, 

404 attributes_namer = attributes_namer, 

405 arguments = { f"{leveli}_surveyor": implementation_core }, 

406 level = leveli, name = 'surveyor', 

407 default = _behaviors.survey_visible_attributes ) 

408 

409 if original is None: 

410 

411 def survey_with_super( 

412 self: object 

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

414 ligation = super( cls, self ).__dir__ 

415 # Only enforce behaviors at start of MRO. 

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

417 return core( 

418 self, 

419 ligation = ligation, 

420 attributes_namer = attributes_namer, 

421 level = leveli ) 

422 

423 cls.__dir__ = survey_with_super 

424 

425 else: 

426 

427 @__.funct.wraps( original ) 

428 def survey_with_original( 

429 self: object 

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

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

432 # Only enforce behaviors at start of MRO. 

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

434 return core( 

435 self, 

436 ligation = ligation, 

437 attributes_namer = attributes_namer, 

438 level = leveli ) 

439 

440 cls.__dir__ = survey_with_original 

441 

442 return cls 

443 

444 return decorate 

445 

446 

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

448def dataclass_with_standard_behaviors( # noqa: PLR0913 

449 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

450 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

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

452 assigner_core: __.typx.Optional[ _nomina.AssignerCore ] = None, 

453 deleter_core: __.typx.Optional[ _nomina.DeleterCore ] = None, 

454 surveyor_core: __.typx.Optional[ _nomina.SurveyorCore ] = None, 

455 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default, 

456 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default, 

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

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

459 ''' Dataclass decorator factory. ''' 

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

461 _produce_instances_decorators( 

462 attributes_namer = attributes_namer, 

463 error_class_provider = error_class_provider, 

464 assigner_core = assigner_core, 

465 deleter_core = deleter_core, 

466 surveyor_core = surveyor_core, 

467 mutables = mutables, 

468 visibles = visibles ) ) 

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

470 _produce_instances_decoration_preparers( 

471 attributes_namer = attributes_namer, 

472 error_class_provider = error_class_provider, 

473 class_preparer = prepare_dataclass_for_instances ) ) 

474 return decoration_by( 

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

476 

477 

478def with_standard_behaviors( # noqa: PLR0913 

479 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname, 

480 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class, 

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

482 assigner_core: __.typx.Optional[ _nomina.AssignerCore ] = None, 

483 deleter_core: __.typx.Optional[ _nomina.DeleterCore ] = None, 

484 surveyor_core: __.typx.Optional[ _nomina.SurveyorCore ] = None, 

485 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default, 

486 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default, 

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

488 ''' Class decorator factory. ''' 

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

490 _produce_instances_decorators( 

491 attributes_namer = attributes_namer, 

492 error_class_provider = error_class_provider, 

493 assigner_core = assigner_core, 

494 deleter_core = deleter_core, 

495 surveyor_core = surveyor_core, 

496 mutables = mutables, 

497 visibles = visibles ) ) 

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

499 _produce_instances_decoration_preparers( 

500 attributes_namer = attributes_namer, 

501 error_class_provider = error_class_provider ) ) 

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

503 

504 

505def _produce_instances_decoration_preparers( 

506 attributes_namer: _nomina.AttributesNamer, 

507 error_class_provider: _nomina.ErrorClassProvider, 

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

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

510 ''' Produces processors for standard decorators. ''' 

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

512 if class_preparer is not None: 

513 preprocessors.append( 

514 __.funct.partial( 

515 class_preparer, attributes_namer = attributes_namer ) ) 

516 return tuple( preprocessors ) 

517 

518 

519def _produce_instances_decorators( # noqa: PLR0913 

520 attributes_namer: _nomina.AttributesNamer, 

521 error_class_provider: _nomina.ErrorClassProvider, 

522 assigner_core: __.typx.Optional[ _nomina.AssignerCore ], 

523 deleter_core: __.typx.Optional[ _nomina.DeleterCore ], 

524 surveyor_core: __.typx.Optional[ _nomina.SurveyorCore ], 

525 mutables: _nomina.BehaviorExclusionVerifiersOmni, 

526 visibles: _nomina.BehaviorExclusionVerifiersOmni, 

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

528 ''' Produces standard decorators. ''' 

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

530 decorators.append( 

531 produce_instances_initialization_decorator( 

532 attributes_namer = attributes_namer, 

533 assigner_core = assigner_core, 

534 deleter_core = deleter_core, 

535 surveyor_core = surveyor_core, 

536 mutables = mutables, visibles = visibles ) ) 

537 decorators.append( 

538 produce_attributes_assignment_decorator( 

539 level = 'instances', 

540 attributes_namer = attributes_namer, 

541 error_class_provider = error_class_provider, 

542 implementation_core = assigner_core ) ) 

543 decorators.append( 

544 produce_attributes_deletion_decorator( 

545 level = 'instances', 

546 attributes_namer = attributes_namer, 

547 error_class_provider = error_class_provider, 

548 implementation_core = deleter_core ) ) 

549 decorators.append( 

550 produce_attributes_surveillance_decorator( 

551 level = 'instances', 

552 attributes_namer = attributes_namer, 

553 implementation_core = surveyor_core ) ) 

554 return decorators