Coverage for sources/classcore/standard/decorators.py: 99%
122 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 23:23 +0000
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-29 23:23 +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 __future__ import annotations
29from .. import factories as _factories
30from .. import utilities as _utilities
31from ..decorators import (
32 decoration_by,
33 produce_class_construction_decorator,
34 produce_class_initialization_decorator,
35)
36from . import __
37from . import behaviors as _behaviors
38from . import nomina as _nomina
41_U = __.typx.TypeVar( '_U' )
44_dataclass_core = __.dcls.dataclass( kw_only = True, slots = True )
47def _produce_class_factory_core(
48 attributes_namer: _nomina.AttributesNamer,
49 error_class_provider: _nomina.ErrorClassProvider,
50) -> tuple[ _nomina.ClassConstructor, _nomina.ClassInitializer ]:
51 preprocessors = (
52 _behaviors.produce_class_construction_preprocessor(
53 attributes_namer = attributes_namer ), )
54 postprocessors = (
55 _behaviors.produce_class_construction_postprocessor(
56 attributes_namer = attributes_namer ), )
57 completers = (
58 _behaviors.produce_class_initialization_completer(
59 attributes_namer = attributes_namer ), )
60 constructor = (
61 _factories.produce_class_constructor(
62 attributes_namer = attributes_namer,
63 preprocessors = preprocessors,
64 postprocessors = postprocessors ) )
65 initializer = (
66 _factories.produce_class_initializer(
67 attributes_namer = attributes_namer,
68 completers = completers ) )
69 return constructor, initializer
72def prepare_dataclass_for_instances(
73 cls: type,
74 decorators: _nomina.DecoratorsMutable, /, *,
75 attributes_namer: _nomina.AttributesNamer,
76) -> None:
77 ''' Annotates dataclass in support of instantiation machinery. '''
78 annotations = __.inspect.get_annotations( cls )
79 behaviors_name = attributes_namer( 'instance', 'behaviors' )
80 annotations[ behaviors_name ] = set[ str ]
81 setattr( cls, '__annotations__', annotations ) # in case of absence
82 setattr( cls, behaviors_name, __.dcls.field( init = False ) )
85def produce_class_factory_decorators(
86 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
87 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
88 assigner_core: _nomina.AssignerCore = (
89 _behaviors.assign_attribute_if_mutable ),
90 deleter_core: _nomina.DeleterCore = (
91 _behaviors.delete_attribute_if_mutable ),
92 surveyor_core: _nomina.SurveyorCore = (
93 _behaviors.survey_visible_attributes ),
94) -> _nomina.Decorators:
95 decorators: list[ _nomina.Decorator ] = [ ]
96 constructor, initializer = (
97 _produce_class_factory_core(
98 attributes_namer = attributes_namer,
99 error_class_provider = error_class_provider ) )
100 decorators.append(
101 produce_class_construction_decorator(
102 attributes_namer = attributes_namer,
103 constructor = constructor ) )
104 decorators.append(
105 produce_class_initialization_decorator(
106 attributes_namer = attributes_namer,
107 initializer = initializer ) )
108 decorators.append(
109 produce_attributes_assignment_decorator(
110 level = 'class',
111 attributes_namer = attributes_namer,
112 error_class_provider = error_class_provider,
113 implementation_core = assigner_core ) )
114 decorators.append(
115 produce_attributes_deletion_decorator(
116 level = 'class',
117 attributes_namer = attributes_namer,
118 error_class_provider = error_class_provider,
119 implementation_core = deleter_core ) )
120 decorators.append(
121 produce_attributes_surveillance_decorator(
122 level = 'class',
123 attributes_namer = attributes_namer,
124 implementation_core = surveyor_core ) )
125 return decorators
128def produce_instances_initialization_decorator(
129 attributes_namer: _nomina.AttributesNamer,
130 mutables: _nomina.BehaviorExclusionVerifiersOmni,
131 visibles: _nomina.BehaviorExclusionVerifiersOmni,
132) -> _nomina.Decorator:
133 def decorate( cls: type[ _U ] ) -> type[ _U ]:
134 initializer_name = attributes_namer( 'instances', 'initializer' )
135 extant = getattr( cls, initializer_name, None )
136 original = getattr( cls, '__init__' )
137 if extant is original: return cls
138 behaviors: set[ str ] = set( )
139 behaviors_name = attributes_namer( 'instance', 'behaviors' )
140 _behaviors.record_behavior(
141 cls, attributes_namer = attributes_namer,
142 level = 'instances', basename = 'mutables',
143 label = _behaviors.immutability_label, behaviors = behaviors,
144 verifiers = mutables )
145 _behaviors.record_behavior(
146 cls, attributes_namer = attributes_namer,
147 level = 'instances', basename = 'visibles',
148 label = _behaviors.concealment_label, behaviors = behaviors,
149 verifiers = visibles )
151 @__.funct.wraps( original )
152 def initialize(
153 self: object, *posargs: __.typx.Any, **nomargs: __.typx.Any
154 ) -> None:
155 original( self, *posargs, **nomargs )
156 behaviors_ = _utilities.getattr0( self, behaviors_name, set( ) )
157 if not behaviors_: setattr( self, behaviors_name, behaviors_ )
158 behaviors_.update( behaviors )
160 setattr( cls, initializer_name, initialize )
161 cls.__init__ = initialize
162 return cls
164 return decorate
167def produce_attributes_assignment_decorator(
168 level: str,
169 attributes_namer: _nomina.AttributesNamer,
170 error_class_provider: _nomina.ErrorClassProvider,
171 implementation_core: _nomina.AssignerCore,
172) -> _nomina.Decorator:
173 def decorate( cls: type[ _U ] ) -> type[ _U ]:
174 assigner_name = attributes_namer( level, 'assigner' )
175 extant = getattr( cls, assigner_name, None )
176 original = getattr( cls, '__setattr__' )
177 if extant is original: return cls
179 @__.funct.wraps( original )
180 def assign( self: object, name: str, value: __.typx.Any ) -> None:
181 implementation_core(
182 self,
183 ligation = __.funct.partial( original, self ),
184 attributes_namer = attributes_namer,
185 error_class_provider = error_class_provider,
186 level = level,
187 name = name, value = value )
189 setattr( cls, assigner_name, assign )
190 cls.__setattr__ = assign
191 return cls
193 return decorate
196def produce_attributes_deletion_decorator(
197 level: str,
198 attributes_namer: _nomina.AttributesNamer,
199 error_class_provider: _nomina.ErrorClassProvider,
200 implementation_core: _nomina.DeleterCore,
201) -> _nomina.Decorator:
202 def decorate( cls: type[ _U ] ) -> type[ _U ]:
203 deleter_name = attributes_namer( level, 'deleter' )
204 extant = getattr( cls, deleter_name, None )
205 original = getattr( cls, '__delattr__' )
206 if extant is original: return cls
208 @__.funct.wraps( original )
209 def delete( self: object, name: str ) -> None:
210 implementation_core(
211 self,
212 ligation = __.funct.partial( original, self ),
213 attributes_namer = attributes_namer,
214 error_class_provider = error_class_provider,
215 level = level,
216 name = name )
218 setattr( cls, deleter_name, delete )
219 cls.__delattr__ = delete
220 return cls
222 return decorate
225def produce_attributes_surveillance_decorator(
226 level: str,
227 attributes_namer: _nomina.AttributesNamer,
228 implementation_core: _nomina.SurveyorCore,
229) -> _nomina.Decorator:
230 def decorate( cls: type[ _U ] ) -> type[ _U ]:
231 surveyor_name = attributes_namer( level, 'surveyor' )
232 extant = getattr( cls, surveyor_name, None )
233 original = getattr( cls, '__dir__' )
234 if extant is original: return cls
236 @__.funct.wraps( original )
237 def survey( self: object ) -> __.cabc.Iterable[ str ]:
238 return implementation_core(
239 self,
240 ligation = __.funct.partial( original, self ),
241 attributes_namer = attributes_namer,
242 level = level )
244 setattr( cls, surveyor_name, survey )
245 cls.__dir__ = survey
246 return cls
248 return decorate
251def produce_decorators_factory( # noqa: PLR0913
252 level: str,
253 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
254 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
255 assigner_core: _nomina.AssignerCore = (
256 _behaviors.assign_attribute_if_mutable ),
257 deleter_core: _nomina.DeleterCore = (
258 _behaviors.delete_attribute_if_mutable ),
259 surveyor_core: _nomina.SurveyorCore = (
260 _behaviors.survey_visible_attributes ),
261) -> __.cabc.Callable[
262 [
263 _nomina.BehaviorExclusionVerifiersOmni,
264 _nomina.BehaviorExclusionVerifiersOmni
265 ],
266 _nomina.Decorators
267]:
268 def produce(
269 mutables: _nomina.BehaviorExclusionVerifiersOmni,
270 visibles: _nomina.BehaviorExclusionVerifiersOmni,
271 ) -> _nomina.Decorators:
272 ''' Produces standard decorators. '''
273 decorators: list[ _nomina.Decorator ] = [ ]
274 decorators.append(
275 produce_instances_initialization_decorator(
276 attributes_namer = attributes_namer,
277 mutables = mutables, visibles = visibles ) )
278 if mutables != '*':
279 decorators.append(
280 produce_attributes_assignment_decorator(
281 level = level,
282 attributes_namer = attributes_namer,
283 error_class_provider = error_class_provider,
284 implementation_core = assigner_core ) )
285 decorators.append(
286 produce_attributes_deletion_decorator(
287 level = level,
288 attributes_namer = attributes_namer,
289 error_class_provider = error_class_provider,
290 implementation_core = deleter_core ) )
291 if visibles != '*': 291 ↛ 297line 291 didn't jump to line 297 because the condition on line 291 was always true
292 decorators.append(
293 produce_attributes_surveillance_decorator(
294 level = level,
295 attributes_namer = attributes_namer,
296 implementation_core = surveyor_core ) )
297 return decorators
299 return produce
302def produce_decoration_preparers_factory(
303 attributes_namer: _nomina.AttributesNamer = __.calculate_attrname,
304 error_class_provider: _nomina.ErrorClassProvider = __.provide_error_class,
305 class_preparer: __.typx.Optional[ _nomina.ClassPreparer ] = None,
306) -> __.cabc.Callable[ [ ], _nomina.DecorationPreparers ]:
307 def produce( ) -> _nomina.DecorationPreparers:
308 ''' Produces processors for standard decorators. '''
309 preprocessors: list[ _nomina.DecorationPreparer ] = [ ]
310 if class_preparer is not None:
311 preprocessors.append(
312 __.funct.partial(
313 class_preparer,
314 attributes_namer = attributes_namer ) )
315 return tuple( preprocessors )
317 return produce
320class_factory_decorators = produce_class_factory_decorators( )
323@__.typx.dataclass_transform( frozen_default = True, kw_only_default = True )
324def dataclass_with_standard_behaviors(
325 decorators: _nomina.Decorators = ( ),
326 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default,
327 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default,
328) -> _nomina.Decorator:
329 # https://github.com/microsoft/pyright/discussions/10344
330 ''' Dataclass decorator factory. '''
331 decorators_factory = produce_decorators_factory( level = 'instances' )
332 decorators_ = decorators_factory( mutables, visibles )
333 preparers_factory = produce_decoration_preparers_factory(
334 class_preparer = prepare_dataclass_for_instances )
335 preparers = preparers_factory( )
336 return decoration_by(
337 *decorators, _dataclass_core, *decorators_, preparers = preparers )
340def with_standard_behaviors(
341 decorators: _nomina.Decorators = ( ),
342 mutables: _nomina.BehaviorExclusionVerifiersOmni = __.mutables_default,
343 visibles: _nomina.BehaviorExclusionVerifiersOmni = __.visibles_default,
344) -> _nomina.Decorator:
345 ''' Class decorator factory. '''
346 decorators_factory = produce_decorators_factory( level = 'instances' )
347 decorators_ = decorators_factory( mutables, visibles )
348 preparers_factory = produce_decoration_preparers_factory( )
349 preparers = preparers_factory( )
350 return decoration_by( *decorators, *decorators_, preparers = preparers )