Coverage for sources/accretive/classes.py: 100%

78 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-10 04:52 +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''' Accretive classes. ''' 

22 

23 

24from __future__ import annotations 

25 

26from . import __ 

27 

28 

29ClassDecorators: __.a.TypeAlias = ( 

30 __.cabc.Iterable[ __.cabc.Callable[ [ type ], type ] ] ) 

31 

32 

33_behavior = 'accretion' 

34 

35 

36class Class( type ): 

37 ''' Accretive class factory. ''' 

38 

39 def __new__( # pylint: disable=too-many-arguments 

40 factory: type[ type ], 

41 name: str, 

42 bases: tuple[ type, ... ], 

43 namespace: dict[ str, __.a.Any ], *, 

44 decorators: ClassDecorators = ( ), 

45 docstring: __.Optional[ __.a.Nullable[ str ] ] = __.absent, 

46 **args: __.a.Any 

47 ) -> Class: 

48 class_ = type.__new__( 

49 factory, name, bases, namespace, **args ) 

50 return _class__new__( # type: ignore 

51 class_, decorators = decorators, docstring = docstring ) 

52 

53 def __init__( selfclass, *posargs: __.a.Any, **nomargs: __.a.Any ): 

54 super( ).__init__( *posargs, **nomargs ) 

55 _class__init__( selfclass ) 

56 

57 def __delattr__( selfclass, name: str ) -> None: 

58 if not _class__delattr__( selfclass, name ): 

59 super( ).__delattr__( name ) 

60 

61 def __setattr__( selfclass, name: str, value: __.a.Any ) -> None: 

62 if not _class__setattr__( selfclass, name ): 

63 super( ).__setattr__( name, value ) 

64 

65Class.__doc__ = __.generate_docstring( 

66 Class, 

67 'description of class factory class', 

68 'class attributes accretion' 

69) 

70 

71 

72class ABCFactory( __.ABCFactory ): # type: ignore 

73 ''' Accretive abstract base class factory. ''' 

74 

75 def __new__( # pylint: disable=too-many-arguments 

76 factory: type[ type ], 

77 name: str, 

78 bases: tuple[ type, ... ], 

79 namespace: dict[ str, __.a.Any ], *, 

80 decorators: ClassDecorators = ( ), 

81 docstring: __.Optional[ __.a.Nullable[ str ] ] = __.absent, 

82 **args: __.a.Any 

83 ) -> ABCFactory: 

84 class_ = __.ABCFactory.__new__( 

85 factory, name, bases, namespace, **args ) 

86 return _class__new__( # type: ignore 

87 class_, decorators = decorators, docstring = docstring ) 

88 

89 def __init__( selfclass, *posargs: __.a.Any, **nomargs: __.a.Any ): 

90 super( ).__init__( *posargs, **nomargs ) 

91 _class__init__( selfclass ) 

92 

93 def __delattr__( selfclass, name: str ) -> None: 

94 if not _class__delattr__( selfclass, name ): 

95 super( ).__delattr__( name ) 

96 

97 def __setattr__( selfclass, name: str, value: __.a.Any ) -> None: 

98 if not _class__setattr__( selfclass, name ): 

99 super( ).__setattr__( name, value ) 

100 

101ABCFactory.__doc__ = __.generate_docstring( 

102 ABCFactory, 

103 'description of class factory class', 

104 'class attributes accretion' 

105) 

106 

107 

108class ProtocolClass( __.a.Protocol.__class__ ): # type: ignore 

109 ''' Accretive protocol class factory. ''' 

110 

111 def __new__( # pylint: disable=too-many-arguments 

112 factory: type[ type ], 

113 name: str, 

114 bases: tuple[ type, ... ], 

115 namespace: dict[ str, __.a.Any ], *, 

116 decorators: ClassDecorators = ( ), 

117 docstring: __.Optional[ __.a.Nullable[ str ] ] = __.absent, 

118 **args: __.a.Any 

119 ) -> ProtocolClass: 

120 class_ = __.a.Protocol.__class__.__new__( 

121 factory, name, bases, namespace, **args ) 

122 return _class__new__( # type: ignore 

123 class_, decorators = decorators, docstring = docstring ) 

124 

125 def __init__( selfclass, *posargs: __.a.Any, **nomargs: __.a.Any ): 

126 super( ).__init__( *posargs, **nomargs ) 

127 _class__init__( selfclass ) 

128 

129 def __delattr__( selfclass, name: str ) -> None: 

130 if not _class__delattr__( selfclass, name ): 

131 super( ).__delattr__( name ) 

132 

133 def __setattr__( selfclass, name: str, value: __.a.Any ) -> None: 

134 if not _class__setattr__( selfclass, name ): 

135 super( ).__setattr__( name, value ) 

136 

137ProtocolClass.__doc__ = __.generate_docstring( 

138 ProtocolClass, 

139 'description of class factory class', 

140 'class attributes accretion' 

141) 

142 

143 

144def _class__new__( 

145 original: type, 

146 decorators: ClassDecorators = ( ), 

147 docstring: __.Optional[ __.a.Nullable[ str ] ] = __.absent, 

148) -> type: 

149 # Handle decorators similar to immutable implementation. 

150 # Some decorators create new classes, which invokes this method again. 

151 # Short-circuit to prevent recursive decoration and other tangles. 

152 class_decorators_ = original.__dict__.get( '_class_decorators_', [ ] ) 

153 if class_decorators_: return original 

154 if not __.is_absent( docstring ): original.__doc__ = docstring 

155 setattr( original, '_class_decorators_', class_decorators_ ) 

156 reproduction = original 

157 for decorator in decorators: 

158 class_decorators_.append( decorator ) 

159 reproduction = decorator( original ) 

160 if original is not reproduction: 

161 __.repair_class_reproduction( original, reproduction ) 

162 original = reproduction 

163 class_decorators_.clear( ) # Flag '__init__' to enable accretion 

164 return reproduction 

165 

166 

167def _class__init__( class_: type ) -> None: 

168 # Some metaclasses add class attributes in '__init__' method. 

169 # So, we wait until last possible moment to set accretion. 

170 if class_.__dict__.get( '_class_decorators_' ): return 

171 del class_._class_decorators_ 

172 if ( class_behaviors := class_.__dict__.get( '_class_behaviors_' ) ): 

173 class_behaviors.add( _behavior ) 

174 else: setattr( class_, '_class_behaviors_', { _behavior } ) 

175 

176 

177def _class__delattr__( class_: type, name: str ) -> bool: 

178 # Consult class attributes dictionary to ignore accretive base classes. 

179 if _behavior not in class_.__dict__.get( '_class_behaviors_', ( ) ): 

180 return False 

181 from .exceptions import IndelibleAttributeError 

182 raise IndelibleAttributeError( name ) 

183 

184 

185def _class__setattr__( class_: type, name: str ) -> bool: 

186 # Consult class attributes dictionary to ignore accretive base classes. 

187 if _behavior not in class_.__dict__.get( '_class_behaviors_', ( ) ): 

188 return False 

189 if hasattr( class_, name ): 

190 from .exceptions import IndelibleAttributeError 

191 raise IndelibleAttributeError( name ) 

192 return False # Allow setting new attributes