Coverage for sources/classcore/standard/decorators.py: 100%
140 statements
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-05 22:28 +0000
« prev ^ index » next coverage.py v7.8.2, created at 2025-06-05 22:28 +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 behaviors_name_m = _utilities.mangle_name( cls, behaviors_name )
53 annotations[ behaviors_name_m ] = set[ str ]
54 setattr( cls, '__annotations__', annotations ) # in case of absence
55 setattr( cls, behaviors_name_m, __.dcls.field( init = False ) )
58def apply_cfc_dynadoc_configuration(
59 clscls: type[ __.T ], /,
60 attributes_namer: _nomina.AttributesNamer,
61 configuration: _nomina.DynadocConfiguration,
62) -> None:
63 ''' Stores Dynadoc configuration on metaclass. '''
64 configuration_name = attributes_namer( 'classes', 'dynadoc_configuration' )
65 setattr( clscls, configuration_name, configuration )
68def apply_cfc_constructor(
69 clscls: type[ __.T ], /, attributes_namer: _nomina.AttributesNamer
70) -> None:
71 ''' Injects '__new__' method into metaclass. '''
72 preprocessors = (
73 _behaviors.produce_class_construction_preprocessor(
74 attributes_namer = attributes_namer ), )
75 postprocessors = (
76 _behaviors.produce_class_construction_postprocessor(
77 attributes_namer = attributes_namer ), )
78 constructor: _nomina.ClassConstructor[ __.T ] = (
79 _factories.produce_class_constructor(
80 attributes_namer = attributes_namer,
81 preprocessors = preprocessors,
82 postprocessors = postprocessors ) )
83 decorator = produce_class_construction_decorator(
84 attributes_namer = attributes_namer, constructor = constructor )
85 decorator( clscls )
88def apply_cfc_initializer(
89 clscls: type[ __.T ], attributes_namer: _nomina.AttributesNamer
90) -> None:
91 ''' Injects '__init__' method into metaclass. '''
92 completers = (
93 _behaviors.produce_class_initialization_completer(
94 attributes_namer = attributes_namer ), )
95 initializer = (
96 _factories.produce_class_initializer(
97 attributes_namer = attributes_namer,
98 completers = completers ) )
99 decorator = produce_class_initialization_decorator(
100 attributes_namer = attributes_namer, initializer = initializer )
101 decorator( clscls )
104def apply_cfc_attributes_assigner(
105 clscls: type[ __.T ], /,
106 attributes_namer: _nomina.AttributesNamer,
107 error_class_provider: _nomina.ErrorClassProvider,
108 implementation_core: _nomina.AssignerCore,
109) -> None:
110 ''' Injects '__setattr__' method into metaclass. '''
111 decorator = produce_attributes_assignment_decorator(
112 level = 'class',
113 attributes_namer = attributes_namer,
114 error_class_provider = error_class_provider,
115 implementation_core = implementation_core )
116 decorator( clscls )
119def apply_cfc_attributes_deleter(
120 clscls: type[ __.T ], /,
121 attributes_namer: _nomina.AttributesNamer,
122 error_class_provider: _nomina.ErrorClassProvider,
123 implementation_core: _nomina.DeleterCore,
124) -> None:
125 ''' Injects '__delattr__' method into metaclass. '''
126 decorator = produce_attributes_deletion_decorator(
127 level = 'class',
128 attributes_namer = attributes_namer,
129 error_class_provider = error_class_provider,
130 implementation_core = implementation_core )
131 decorator( clscls )
134def apply_cfc_attributes_surveyor(
135 clscls: type[ __.T ],
136 attributes_namer: _nomina.AttributesNamer,
137 implementation_core: _nomina.SurveyorCore,
138) -> None:
139 ''' Injects '__dir__' method into metaclass. '''
140 decorator = produce_attributes_surveillance_decorator(
141 level = 'class',
142 attributes_namer = attributes_namer,
143 implementation_core = implementation_core )
144 decorator( clscls )
147def class_factory( # noqa: PLR0913
148 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
149 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
150 assigner_core: _nomina.AssignerCore = (
151 _behaviors.assign_attribute_if_mutable ),
152 deleter_core: _nomina.DeleterCore = (
153 _behaviors.delete_attribute_if_mutable ),
154 surveyor_core: _nomina.SurveyorCore = (
155 _behaviors.survey_visible_attributes ),
156 dynadoc_configuration: __.cabc.Mapping[ str, __.typx.Any ] = (
157 _dynadoc_configuration ),
158) -> _nomina.Decorator[ __.T ]:
159 ''' Produces decorator to apply standard behaviors to metaclass. '''
160 def decorate( clscls: type[ __.T ] ) -> type[ __.T ]:
161 apply_cfc_dynadoc_configuration(
162 clscls,
163 attributes_namer = attributes_namer,
164 configuration = dynadoc_configuration )
165 apply_cfc_constructor( clscls, attributes_namer = attributes_namer )
166 apply_cfc_initializer( clscls, attributes_namer = attributes_namer )
167 apply_cfc_attributes_assigner(
168 clscls,
169 attributes_namer = attributes_namer,
170 error_class_provider = error_class_provider,
171 implementation_core = assigner_core )
172 apply_cfc_attributes_deleter(
173 clscls,
174 attributes_namer = attributes_namer,
175 error_class_provider = error_class_provider,
176 implementation_core = deleter_core )
177 apply_cfc_attributes_surveyor(
178 clscls,
179 attributes_namer = attributes_namer,
180 implementation_core = surveyor_core )
181 return clscls
183 return decorate
186def produce_instances_initialization_decorator(
187 attributes_namer: _nomina.AttributesNamer,
188 mutables: _nomina.BehaviorExclusionVerifiersOmni,
189 visibles: _nomina.BehaviorExclusionVerifiersOmni,
190) -> _nomina.Decorator[ __.U ]:
191 ''' Produces decorator to inject '__init__' method into class. '''
192 def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
193 initializer_name = attributes_namer( 'instances', 'initializer' )
194 extant = getattr( cls, initializer_name, None )
195 original = getattr( cls, '__init__' )
196 if extant is original: return cls
197 behaviors: set[ str ] = set( )
198 behaviors_name = attributes_namer( 'instance', 'behaviors' )
199 behaviors_name_m = _utilities.mangle_name( cls, behaviors_name )
200 _behaviors.record_behavior(
201 cls, attributes_namer = attributes_namer,
202 level = 'instances', basename = 'mutables',
203 label = _nomina.immutability_label, behaviors = behaviors,
204 verifiers = mutables )
205 _behaviors.record_behavior(
206 cls, attributes_namer = attributes_namer,
207 level = 'instances', basename = 'visibles',
208 label = _nomina.concealment_label, behaviors = behaviors,
209 verifiers = visibles )
211 @__.funct.wraps( original )
212 def initialize(
213 self: object, *posargs: __.typx.Any, **nomargs: __.typx.Any
214 ) -> None:
215 original( self, *posargs, **nomargs )
216 behaviors_: set[ str ] = getattr( self, behaviors_name_m, set( ) )
217 behaviors_.update( behaviors )
218 setattr( self, behaviors_name_m, frozenset( behaviors_ ) )
220 setattr( cls, initializer_name, initialize )
221 cls.__init__ = initialize
222 return cls
224 return decorate
227def produce_attributes_assignment_decorator(
228 level: str,
229 attributes_namer: _nomina.AttributesNamer,
230 error_class_provider: _nomina.ErrorClassProvider,
231 implementation_core: _nomina.AssignerCore,
232) -> _nomina.Decorator[ __.U ]:
233 ''' Produces decorator to inject '__setattr__' method into class. '''
234 def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
235 assigner_name = attributes_namer( level, 'assigner' )
236 extant = getattr( cls, assigner_name, None )
237 original = getattr( cls, '__setattr__' )
238 if extant is original: return cls
240 @__.funct.wraps( original )
241 def assign( self: object, name: str, value: __.typx.Any ) -> None:
242 implementation_core(
243 self,
244 ligation = __.funct.partial( original, self ),
245 attributes_namer = attributes_namer,
246 error_class_provider = error_class_provider,
247 level = level,
248 name = name, value = value )
250 setattr( cls, assigner_name, assign )
251 cls.__setattr__ = assign
252 return cls
254 return decorate
257def produce_attributes_deletion_decorator(
258 level: str,
259 attributes_namer: _nomina.AttributesNamer,
260 error_class_provider: _nomina.ErrorClassProvider,
261 implementation_core: _nomina.DeleterCore,
262) -> _nomina.Decorator[ __.U ]:
263 ''' Produces decorator to inject '__delattr__' method into class. '''
264 def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
265 deleter_name = attributes_namer( level, 'deleter' )
266 extant = getattr( cls, deleter_name, None )
267 original = getattr( cls, '__delattr__' )
268 if extant is original: return cls
270 @__.funct.wraps( original )
271 def delete( self: object, name: str ) -> None:
272 implementation_core(
273 self,
274 ligation = __.funct.partial( original, self ),
275 attributes_namer = attributes_namer,
276 error_class_provider = error_class_provider,
277 level = level,
278 name = name )
280 setattr( cls, deleter_name, delete )
281 cls.__delattr__ = delete
282 return cls
284 return decorate
287def produce_attributes_surveillance_decorator(
288 level: str,
289 attributes_namer: _nomina.AttributesNamer,
290 implementation_core: _nomina.SurveyorCore,
291) -> _nomina.Decorator[ __.U ]:
292 ''' Produces decorator to inject '__dir__' method into class. '''
293 def decorate( cls: type[ __.U ] ) -> type[ __.U ]:
294 surveyor_name = attributes_namer( level, 'surveyor' )
295 extant = getattr( cls, surveyor_name, None )
296 original = getattr( cls, '__dir__' )
297 if extant is original: return cls
299 @__.funct.wraps( original )
300 def survey( self: object ) -> __.cabc.Iterable[ str ]:
301 return implementation_core(
302 self,
303 ligation = __.funct.partial( original, self ),
304 attributes_namer = attributes_namer,
305 level = level )
307 setattr( cls, surveyor_name, survey )
308 cls.__dir__ = survey
309 return cls
311 return decorate
314def produce_decorators_factory( # noqa: PLR0913
315 level: str,
316 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
317 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
318 assigner_core: _nomina.AssignerCore = (
319 _behaviors.assign_attribute_if_mutable ),
320 deleter_core: _nomina.DeleterCore = (
321 _behaviors.delete_attribute_if_mutable ),
322 surveyor_core: _nomina.SurveyorCore = (
323 _behaviors.survey_visible_attributes ),
324) -> __.cabc.Callable[
325 [
326 _nomina.BehaviorExclusionVerifiersOmni,
327 _nomina.BehaviorExclusionVerifiersOmni
328 ],
329 _nomina.Decorators[ __.U ]
330]:
331 ''' Produces decorators to imbue class with standard behaviors. '''
332 def produce(
333 mutables: _nomina.BehaviorExclusionVerifiersOmni,
334 visibles: _nomina.BehaviorExclusionVerifiersOmni,
335 ) -> _nomina.Decorators[ __.U ]:
336 ''' Produces standard decorators. '''
337 decorators: list[ _nomina.Decorator[ __.U ] ] = [ ]
338 decorators.append(
339 produce_instances_initialization_decorator(
340 attributes_namer = attributes_namer,
341 mutables = mutables, visibles = visibles ) )
342 if mutables != '*':
343 decorators.append(
344 produce_attributes_assignment_decorator(
345 level = level,
346 attributes_namer = attributes_namer,
347 error_class_provider = error_class_provider,
348 implementation_core = assigner_core ) )
349 decorators.append(
350 produce_attributes_deletion_decorator(
351 level = level,
352 attributes_namer = attributes_namer,
353 error_class_provider = error_class_provider,
354 implementation_core = deleter_core ) )
355 if visibles != '*':
356 decorators.append(
357 produce_attributes_surveillance_decorator(
358 level = level,
359 attributes_namer = attributes_namer,
360 implementation_core = surveyor_core ) )
361 return decorators
363 return produce
366def produce_decoration_preparers_factory(
367 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
368 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
369 class_preparer: __.typx.Optional[ _nomina.ClassPreparer ] = None,
370) -> _nomina.DecorationPreparersFactory[ __.U ]:
371 ''' Produces factory to produce class decoration preparers.
373 E.g., a preparer needs to inject special annotations to ensure
374 compatibility with standard behaviors before
375 :py:func:`dataclasses.dataclass` decorates a class.
376 '''
377 def produce( ) -> _nomina.DecorationPreparers[ __.U ]:
378 ''' Produces processors for standard decorators. '''
379 preprocessors: list[ _nomina.DecorationPreparer[ __.U ] ] = [ ]
380 if class_preparer is not None:
381 preprocessors.append(
382 __.funct.partial(
383 class_preparer,
384 attributes_namer = attributes_namer ) )
385 return tuple( preprocessors )
387 return produce
390@__.typx.dataclass_transform( frozen_default = True, kw_only_default = True )
391def dataclass_with_standard_behaviors(
392 decorators: _nomina.Decorators[ __.U ] = ( ),
393 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default,
394 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default,
395) -> _nomina.Decorator[ __.U ]:
396 # https://github.com/microsoft/pyright/discussions/10344
397 ''' Dataclass decorator factory. '''
398 decorators_factory = produce_decorators_factory( level = 'instances' )
399 decorators_: _nomina.Decorators[ __.U ] = (
400 decorators_factory( mutables, visibles ) )
401 preparers_factory = produce_decoration_preparers_factory(
402 class_preparer = prepare_dataclass_for_instances )
403 preparers: _nomina.DecorationPreparers[ __.U ] = preparers_factory( )
404 return decoration_by(
405 *decorators, _dataclass_core, *decorators_, preparers = preparers )
408def with_standard_behaviors(
409 decorators: _nomina.Decorators[ __.U ] = ( ),
410 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default,
411 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default,
412) -> _nomina.Decorator[ __.U ]:
413 ''' Class decorator factory. '''
414 decorators_factory = produce_decorators_factory( level = 'instances' )
415 decorators_: _nomina.Decorators[ __.U ] = (
416 decorators_factory( mutables, visibles ) )
417 preparers_factory = produce_decoration_preparers_factory( )
418 preparers: _nomina.DecorationPreparers[ __.U ] = preparers_factory( )
419 return decoration_by( *decorators, *decorators_, preparers = preparers )