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
« 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 -*-
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_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 )
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 )
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 )
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 )
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 )
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 )
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
188 return decorate
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 )
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__' )
233 if original is None:
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_ ) )
247 cls.__init__ = initialize_with_super
249 else:
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_ ) )
264 cls.__init__ = initialize_with_original
266 return cls
268 return decorate
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 )
288 if original is None:
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 )
306 cls.__setattr__ = assign_with_super
308 else:
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 )
327 cls.__setattr__ = assign_with_original
329 return cls
331 return decorate
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 )
351 if original is None:
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 )
367 cls.__delattr__ = delete_with_super
369 else:
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 )
386 cls.__delattr__ = delete_with_original
388 return cls
390 return decorate
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 )
409 if original is None:
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 )
423 cls.__dir__ = survey_with_super
425 else:
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 )
440 cls.__dir__ = survey_with_original
442 return cls
444 return decorate
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 )
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 )
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 )
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