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

1# vim: set filetype=python fileencoding=utf-8: 

2# -*- coding: utf-8 -*- 

3 

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#============================================================================# 

19 

20 

21''' Standard decorators. ''' 

22# TODO? Add attribute value transformer as standard decorator argument. 

23 

24# ruff: noqa: F401 

25 

26 

27from __future__ import annotations 

28 

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 

39 

40 

41_U = __.typx.TypeVar( '_U' ) 

42 

43 

44_dataclass_core = __.dcls.dataclass( kw_only = True, slots = True ) 

45 

46 

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 

70 

71 

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 ) ) 

83 

84 

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 

126 

127 

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 ) 

150 

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 ) 

159 

160 setattr( cls, initializer_name, initialize ) 

161 cls.__init__ = initialize 

162 return cls 

163 

164 return decorate 

165 

166 

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 

178 

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 ) 

188 

189 setattr( cls, assigner_name, assign ) 

190 cls.__setattr__ = assign 

191 return cls 

192 

193 return decorate 

194 

195 

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 

207 

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 ) 

217 

218 setattr( cls, deleter_name, delete ) 

219 cls.__delattr__ = delete 

220 return cls 

221 

222 return decorate 

223 

224 

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 

235 

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 ) 

243 

244 setattr( cls, surveyor_name, survey ) 

245 cls.__dir__ = survey 

246 return cls 

247 

248 return decorate 

249 

250 

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 

298 

299 return produce 

300 

301 

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 ) 

316 

317 return produce 

318 

319 

320class_factory_decorators = produce_class_factory_decorators( ) 

321 

322 

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 ) 

338 

339 

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 )