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