Coverage for sources/classcore/standard/decorators.py: 100%
174 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-01 05:36 +0000
« prev ^ index » next coverage.py v7.9.1, created at 2025-07-01 05:36 +0000
1# vim: set filetype=python fileencoding=utf-8:
2# -*- coding: utf-8 -*-
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#============================================================================#
21''' Standard decorators. '''
22# TODO? Add attribute value transformer as standard decorator argument.
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
38_dataclass_core = __.dcls.dataclass( kw_only = True, slots = True )
39_dynadoc_configuration = _dynadoc.produce_dynadoc_configuration( )
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_ = _utilities.mangle_name( cls, behaviors_name )
52 behaviors_name_ = behaviors_name
53 annotations[ behaviors_name_ ] = set[ str ]
54 setattr( cls, '__annotations__', annotations ) # in case of absence
55 setattr( cls, behaviors_name_, __.dcls.field(
56 compare = False, hash = False, init = False, repr = False ) )
59def apply_cfc_core_functions(
60 clscls: type[ __.T ], /,
61 attributes_namer: _nomina.AttributesNamer,
62 assigner_core: __.typx.Optional[ _nomina.AssignerCore ] = None,
63 deleter_core: __.typx.Optional[ _nomina.DeleterCore ] = None,
64 surveyor_core: __.typx.Optional[ _nomina.SurveyorCore ] = None,
65) -> None:
66 ''' Stores core functions on metaclass. '''
67 cores = dict(
68 classes_assigner_core = assigner_core,
69 classes_deleter_core = deleter_core,
70 classes_surveyor_core = surveyor_core )
71 cores_default = dict(
72 assigner = _behaviors.assign_attribute_if_mutable,
73 deleter = _behaviors.delete_attribute_if_mutable,
74 surveyor = _behaviors.survey_visible_attributes )
75 for core_name in ( 'assigner', 'deleter', 'surveyor' ):
76 core_function = _behaviors.access_core_function(
77 clscls,
78 attributes_namer = attributes_namer,
79 arguments = cores,
80 level = 'classes', name = core_name,
81 default = cores_default[ core_name ] )
82 core_aname = attributes_namer( 'classes', f"{core_name}_core" )
83 setattr( clscls, core_aname, core_function )
86def apply_cfc_dynadoc_configuration(
87 clscls: type[ __.T ], /,
88 attributes_namer: _nomina.AttributesNamer,
89 configuration: _nomina.DynadocConfiguration,
90) -> None:
91 ''' Stores Dynadoc configuration on metaclass. '''
92 configuration_name = attributes_namer( 'classes', 'dynadoc_configuration' )
93 setattr( clscls, configuration_name, configuration )
96def apply_cfc_constructor(
97 clscls: type[ __.T ], /,
98 attributes_namer: _nomina.AttributesNamer,
99 error_class_provider: _nomina.ErrorClassProvider,
100) -> None:
101 ''' Injects '__new__' method into metaclass. '''
102 preprocessors = (
103 _behaviors.produce_class_construction_preprocessor(
104 attributes_namer = attributes_namer ), )
105 postprocessors = (
106 _behaviors.produce_class_construction_postprocessor(
107 attributes_namer = attributes_namer,
108 error_class_provider = error_class_provider ), )
109 constructor: _nomina.ClassConstructor[ __.T ] = (
110 _factories.produce_class_constructor(
111 attributes_namer = attributes_namer,
112 preprocessors = preprocessors,
113 postprocessors = postprocessors ) )
114 decorator = produce_class_construction_decorator(
115 attributes_namer = attributes_namer, constructor = constructor )
116 decorator( clscls )
119def apply_cfc_initializer(
120 clscls: type[ __.T ], /, attributes_namer: _nomina.AttributesNamer
121) -> None:
122 ''' Injects '__init__' method into metaclass. '''
123 completers = (
124 _behaviors.produce_class_initialization_completer(
125 attributes_namer = attributes_namer ), )
126 initializer = (
127 _factories.produce_class_initializer(
128 attributes_namer = attributes_namer,
129 completers = completers ) )
130 decorator = produce_class_initialization_decorator(
131 attributes_namer = attributes_namer, initializer = initializer )
132 decorator( clscls )
135def apply_cfc_attributes_assigner(
136 clscls: type[ __.T ], /,
137 attributes_namer: _nomina.AttributesNamer,
138 error_class_provider: _nomina.ErrorClassProvider,
139 implementation_core: __.typx.Optional[ _nomina.AssignerCore ],
140) -> None:
141 ''' Injects '__setattr__' method into metaclass. '''
142 decorator = produce_attributes_assignment_decorator(
143 level = 'classes',
144 attributes_namer = attributes_namer,
145 error_class_provider = error_class_provider,
146 implementation_core = implementation_core )
147 decorator( clscls )
150def apply_cfc_attributes_deleter(
151 clscls: type[ __.T ], /,
152 attributes_namer: _nomina.AttributesNamer,
153 error_class_provider: _nomina.ErrorClassProvider,
154 implementation_core: __.typx.Optional[ _nomina.DeleterCore ],
155) -> None:
156 ''' Injects '__delattr__' method into metaclass. '''
157 decorator = produce_attributes_deletion_decorator(
158 level = 'classes',
159 attributes_namer = attributes_namer,
160 error_class_provider = error_class_provider,
161 implementation_core = implementation_core )
162 decorator( clscls )
165def apply_cfc_attributes_surveyor(
166 clscls: type[ __.T ],
167 attributes_namer: _nomina.AttributesNamer,
168 implementation_core: __.typx.Optional[ _nomina.SurveyorCore ],
169) -> None:
170 ''' Injects '__dir__' method into metaclass. '''
171 decorator = produce_attributes_surveillance_decorator(
172 level = 'classes',
173 attributes_namer = attributes_namer,
174 implementation_core = implementation_core )
175 decorator( clscls )
178def class_factory( # noqa: PLR0913
179 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
180 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
181 assigner_core: __.typx.Optional[ _nomina.AssignerCore ] = None,
182 deleter_core: __.typx.Optional[ _nomina.DeleterCore ] = None,
183 surveyor_core: __.typx.Optional[ _nomina.SurveyorCore ] = None,
184 dynadoc_configuration: __.cabc.Mapping[ str, __.typx.Any ] = (
185 _dynadoc_configuration ),
186) -> _nomina.Decorator[ __.T ]:
187 ''' Produces decorator to apply standard behaviors to metaclass. '''
188 def decorate( clscls: type[ __.T ] ) -> type[ __.T ]:
189 apply_cfc_core_functions(
190 clscls,
191 attributes_namer = attributes_namer,
192 assigner_core = assigner_core,
193 deleter_core = deleter_core,
194 surveyor_core = surveyor_core )
195 apply_cfc_dynadoc_configuration(
196 clscls,
197 attributes_namer = attributes_namer,
198 configuration = dynadoc_configuration )
199 apply_cfc_constructor(
200 clscls,
201 attributes_namer = attributes_namer,
202 error_class_provider = error_class_provider )
203 apply_cfc_initializer( clscls, attributes_namer = attributes_namer )
204 apply_cfc_attributes_assigner(
205 clscls,
206 attributes_namer = attributes_namer,
207 error_class_provider = error_class_provider,
208 implementation_core = assigner_core )
209 apply_cfc_attributes_deleter(
210 clscls,
211 attributes_namer = attributes_namer,
212 error_class_provider = error_class_provider,
213 implementation_core = deleter_core )
214 apply_cfc_attributes_surveyor(
215 clscls,
216 attributes_namer = attributes_namer,
217 implementation_core = surveyor_core )
218 return clscls
220 return decorate
223def produce_instances_initialization_decorator( # noqa: PLR0913
224 attributes_namer: _nomina.AttributesNamer,
225 assigner_core: __.typx.Optional[ _nomina.AssignerCore ],
226 deleter_core: __.typx.Optional[ _nomina.DeleterCore ],
227 surveyor_core: __.typx.Optional[ _nomina.SurveyorCore ],
228 mutables: _nomina.BehaviorExclusionVerifiersOmni,
229 visibles: _nomina.BehaviorExclusionVerifiersOmni,
230) -> _nomina.Decorator[ __.U ]:
231 ''' Produces decorator to inject '__init__' method into class. '''
232 cores = dict(
233 instances_assigner_core = assigner_core,
234 instances_deleter_core = deleter_core,
235 instances_surveyor_core = surveyor_core )
236 cores_default = dict(
237 assigner = _behaviors.assign_attribute_if_mutable,
238 deleter = _behaviors.delete_attribute_if_mutable,
239 surveyor = _behaviors.survey_visible_attributes )
241 def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
242 for core_name in ( 'assigner', 'deleter', 'surveyor' ):
243 core_function = _behaviors.access_core_function(
244 cls,
245 attributes_namer = attributes_namer,
246 arguments = cores,
247 level = 'instances', name = core_name,
248 default = cores_default[ core_name ] )
249 core_aname = attributes_namer( 'instances', f"{core_name}_core" )
250 setattr( cls, core_aname, core_function )
251 behaviors: set[ str ] = set( )
252 behaviors_name = attributes_namer( 'instance', 'behaviors' )
253 _behaviors.record_behavior(
254 cls, attributes_namer = attributes_namer,
255 level = 'instances', basename = 'mutables',
256 label = _nomina.immutability_label, behaviors = behaviors,
257 verifiers = mutables )
258 _behaviors.record_behavior(
259 cls, attributes_namer = attributes_namer,
260 level = 'instances', basename = 'visibles',
261 label = _nomina.concealment_label, behaviors = behaviors,
262 verifiers = visibles )
263 original = cls.__dict__.get( '__init__' )
265 if original is None:
267 def initialize_with_super(
268 self: object, *posargs: __.typx.Any, **nomargs: __.typx.Any
269 ) -> None:
270 super( cls, self ).__init__( *posargs, **nomargs )
271 # Only record behaviors at start of MRO.
272 if cls is not type( self ): return
273 behaviors_: set[ str ] = (
274 _utilities.getattr0( self, behaviors_name, set( ) ) )
275 behaviors_.update( behaviors )
276 _utilities.setattr0(
277 self, behaviors_name, frozenset( behaviors_ ) )
279 cls.__init__ = initialize_with_super
281 else:
283 @__.funct.wraps( original )
284 def initialize_with_original(
285 self: object, *posargs: __.typx.Any, **nomargs: __.typx.Any
286 ) -> None:
287 original( self, *posargs, **nomargs )
288 # Only record behaviors at start of MRO.
289 if cls is not type( self ): return
290 behaviors_: set[ str ] = (
291 _utilities.getattr0( self, behaviors_name, set( ) ) )
292 behaviors_.update( behaviors )
293 _utilities.setattr0(
294 self, behaviors_name, frozenset( behaviors_ ) )
296 cls.__init__ = initialize_with_original
298 return cls
300 return decorate
303def produce_attributes_assignment_decorator(
304 level: str,
305 attributes_namer: _nomina.AttributesNamer,
306 error_class_provider: _nomina.ErrorClassProvider,
307 implementation_core: __.typx.Optional[ _nomina.AssignerCore ],
308) -> _nomina.Decorator[ __.U ]:
309 ''' Produces decorator to inject '__setattr__' method into class. '''
310 def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
311 leveli = 'class' if level == 'classes' else level
312 original = cls.__dict__.get( '__setattr__' )
313 core = _behaviors.access_core_function(
314 cls,
315 attributes_namer = attributes_namer,
316 arguments = { f"{level}_assigner": implementation_core },
317 level = level, name = 'assigner',
318 default = _behaviors.assign_attribute_if_mutable )
320 if original is None:
322 def assign_with_super(
323 self: object, name: str, value: __.typx.Any
324 ) -> None:
325 ligation = super( cls, self ).__setattr__
326 # Only enforce behaviors at start of MRO.
327 if cls is not type( self ):
328 ligation( name, value )
329 return
330 core(
331 self,
332 ligation = ligation,
333 attributes_namer = attributes_namer,
334 error_class_provider = error_class_provider,
335 level = leveli,
336 name = name, value = value )
338 cls.__setattr__ = assign_with_super
340 else:
342 @__.funct.wraps( original )
343 def assign_with_original(
344 self: object, name: str, value: __.typx.Any
345 ) -> None:
346 ligation = __.funct.partial( original, self )
347 # Only enforce behaviors at start of MRO.
348 if cls is not type( self ):
349 ligation( name, value )
350 return
351 core(
352 self,
353 ligation = ligation,
354 attributes_namer = attributes_namer,
355 error_class_provider = error_class_provider,
356 level = leveli,
357 name = name, value = value )
359 cls.__setattr__ = assign_with_original
361 return cls
363 return decorate
366def produce_attributes_deletion_decorator(
367 level: str,
368 attributes_namer: _nomina.AttributesNamer,
369 error_class_provider: _nomina.ErrorClassProvider,
370 implementation_core: __.typx.Optional[ _nomina.DeleterCore ],
371) -> _nomina.Decorator[ __.U ]:
372 ''' Produces decorator to inject '__delattr__' method into class. '''
373 def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
374 leveli = 'class' if level == 'classes' else level
375 original = cls.__dict__.get( '__delattr__' )
376 core = _behaviors.access_core_function(
377 cls,
378 attributes_namer = attributes_namer,
379 arguments = { f"{level}_deleter": implementation_core },
380 level = level, name = 'deleter',
381 default = _behaviors.delete_attribute_if_mutable )
383 if original is None:
385 def delete_with_super( self: object, name: str ) -> None:
386 ligation = super( cls, self ).__delattr__
387 # Only enforce behaviors at start of MRO.
388 if cls is not type( self ):
389 ligation( name )
390 return
391 core(
392 self,
393 ligation = ligation,
394 attributes_namer = attributes_namer,
395 error_class_provider = error_class_provider,
396 level = leveli,
397 name = name )
399 cls.__delattr__ = delete_with_super
401 else:
403 @__.funct.wraps( original )
404 def delete_with_original( self: object, name: str ) -> None:
405 ligation = __.funct.partial( original, self )
406 # Only enforce behaviors at start of MRO.
407 if cls is not type( self ):
408 ligation( name )
409 return
410 core(
411 self,
412 ligation = ligation,
413 attributes_namer = attributes_namer,
414 error_class_provider = error_class_provider,
415 level = leveli,
416 name = name )
418 cls.__delattr__ = delete_with_original
420 return cls
422 return decorate
425def produce_attributes_surveillance_decorator(
426 level: str,
427 attributes_namer: _nomina.AttributesNamer,
428 implementation_core: __.typx.Optional[ _nomina.SurveyorCore ],
429) -> _nomina.Decorator[ __.U ]:
430 ''' Produces decorator to inject '__dir__' method into class. '''
431 def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
432 leveli = 'class' if level == 'classes' else level
433 original = cls.__dict__.get( '__dir__' )
434 core = _behaviors.access_core_function(
435 cls,
436 attributes_namer = attributes_namer,
437 arguments = { f"{level}_surveyor": implementation_core },
438 level = level, name = 'surveyor',
439 default = _behaviors.survey_visible_attributes )
441 if original is None:
443 def survey_with_super(
444 self: object
445 ) -> __.cabc.Iterable[ str ]:
446 ligation = super( cls, self ).__dir__
447 # Only enforce behaviors at start of MRO.
448 if cls is not type( self ): return ligation( )
449 return core(
450 self,
451 ligation = ligation,
452 attributes_namer = attributes_namer,
453 level = leveli )
455 cls.__dir__ = survey_with_super
457 else:
459 @__.funct.wraps( original )
460 def survey_with_original(
461 self: object
462 ) -> __.cabc.Iterable[ str ]:
463 ligation = __.funct.partial( original, self )
464 # Only enforce behaviors at start of MRO.
465 if cls is not type( self ): return ligation( )
466 return core(
467 self,
468 ligation = ligation,
469 attributes_namer = attributes_namer,
470 level = leveli )
472 cls.__dir__ = survey_with_original
474 return cls
476 return decorate
479@__.typx.dataclass_transform( frozen_default = True, kw_only_default = True )
480def dataclass_with_standard_behaviors( # noqa: PLR0913
481 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
482 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
483 decorators: _nomina.Decorators[ __.U ] = ( ),
484 assigner_core: __.typx.Optional[ _nomina.AssignerCore ] = None,
485 deleter_core: __.typx.Optional[ _nomina.DeleterCore ] = None,
486 surveyor_core: __.typx.Optional[ _nomina.SurveyorCore ] = None,
487 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default,
488 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default,
489) -> _nomina.Decorator[ __.U ]:
490 # https://github.com/microsoft/pyright/discussions/10344
491 ''' Dataclass decorator factory. '''
492 decorators_: _nomina.Decorators[ __.U ] = (
493 _produce_instances_decorators(
494 attributes_namer = attributes_namer,
495 error_class_provider = error_class_provider,
496 assigner_core = assigner_core,
497 deleter_core = deleter_core,
498 surveyor_core = surveyor_core,
499 mutables = mutables,
500 visibles = visibles ) )
501 preparers: _nomina.DecorationPreparers[ __.U ] = (
502 _produce_instances_decoration_preparers(
503 attributes_namer = attributes_namer,
504 error_class_provider = error_class_provider,
505 class_preparer = prepare_dataclass_for_instances ) )
506 return decoration_by(
507 *decorators, _dataclass_core, *decorators_, preparers = preparers )
510def with_standard_behaviors( # noqa: PLR0913
511 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
512 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
513 decorators: _nomina.Decorators[ __.U ] = ( ),
514 assigner_core: __.typx.Optional[ _nomina.AssignerCore ] = None,
515 deleter_core: __.typx.Optional[ _nomina.DeleterCore ] = None,
516 surveyor_core: __.typx.Optional[ _nomina.SurveyorCore ] = None,
517 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default,
518 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default,
519) -> _nomina.Decorator[ __.U ]:
520 ''' Class decorator factory. '''
521 decorators_: _nomina.Decorators[ __.U ] = (
522 _produce_instances_decorators(
523 attributes_namer = attributes_namer,
524 error_class_provider = error_class_provider,
525 assigner_core = assigner_core,
526 deleter_core = deleter_core,
527 surveyor_core = surveyor_core,
528 mutables = mutables,
529 visibles = visibles ) )
530 preparers: _nomina.DecorationPreparers[ __.U ] = (
531 _produce_instances_decoration_preparers(
532 attributes_namer = attributes_namer,
533 error_class_provider = error_class_provider ) )
534 return decoration_by( *decorators, *decorators_, preparers = preparers )
537def _produce_instances_decoration_preparers(
538 attributes_namer: _nomina.AttributesNamer,
539 error_class_provider: _nomina.ErrorClassProvider,
540 class_preparer: __.typx.Optional[ _nomina.ClassPreparer ] = None,
541) -> _nomina.DecorationPreparers[ __.U ]:
542 ''' Produces processors for standard decorators. '''
543 preprocessors: list[ _nomina.DecorationPreparer[ __.U ] ] = [ ]
544 if class_preparer is not None:
545 preprocessors.append(
546 __.funct.partial(
547 class_preparer, attributes_namer = attributes_namer ) )
548 return tuple( preprocessors )
551def _produce_instances_decorators( # noqa: PLR0913
552 attributes_namer: _nomina.AttributesNamer,
553 error_class_provider: _nomina.ErrorClassProvider,
554 assigner_core: __.typx.Optional[ _nomina.AssignerCore ],
555 deleter_core: __.typx.Optional[ _nomina.DeleterCore ],
556 surveyor_core: __.typx.Optional[ _nomina.SurveyorCore ],
557 mutables: _nomina.BehaviorExclusionVerifiersOmni,
558 visibles: _nomina.BehaviorExclusionVerifiersOmni,
559) -> _nomina.Decorators[ __.U ]:
560 ''' Produces standard decorators. '''
561 decorators: list[ _nomina.Decorator[ __.U ] ] = [ ]
562 decorators.append(
563 produce_instances_initialization_decorator(
564 attributes_namer = attributes_namer,
565 assigner_core = assigner_core,
566 deleter_core = deleter_core,
567 surveyor_core = surveyor_core,
568 mutables = mutables, visibles = visibles ) )
569 decorators.append(
570 produce_attributes_assignment_decorator(
571 level = 'instances',
572 attributes_namer = attributes_namer,
573 error_class_provider = error_class_provider,
574 implementation_core = assigner_core ) )
575 decorators.append(
576 produce_attributes_deletion_decorator(
577 level = 'instances',
578 attributes_namer = attributes_namer,
579 error_class_provider = error_class_provider,
580 implementation_core = deleter_core ) )
581 decorators.append(
582 produce_attributes_surveillance_decorator(
583 level = 'instances',
584 attributes_namer = attributes_namer,
585 implementation_core = surveyor_core ) )
586 return decorators