Coverage for sources/classcore/standard/decorators.py: 100%
159 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-11 02:31 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-11 02:31 +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.
24# ruff: noqa: F401
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
40_dataclass_core = __.dcls.dataclass( kw_only = True, slots = True )
41_dynadoc_configuration = _dynadoc.produce_dynadoc_configuration( )
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 ) )
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 )
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 )
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 )
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 )
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 )
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 )
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
190 return decorate
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__' )
214 if original is None:
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_ ) )
228 cls.__init__ = initialize_with_super
230 else:
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_ ) )
245 cls.__init__ = initialize_with_original
247 return cls
249 return decorate
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__' )
263 if original is None:
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 )
281 cls.__setattr__ = assign_with_super
283 else:
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 )
302 cls.__setattr__ = assign_with_original
304 return cls
306 return decorate
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__' )
320 if original is None:
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 )
336 cls.__delattr__ = delete_with_super
338 else:
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 )
355 cls.__delattr__ = delete_with_original
357 return cls
359 return decorate
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__' )
372 if original is None:
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 )
386 cls.__dir__ = survey_with_super
388 else:
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 )
403 cls.__dir__ = survey_with_original
405 return cls
407 return decorate
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 )
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 )
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 )
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