Coverage for sources/dynadoc/context.py: 100%

85 statements  

« prev     ^ index     » next       coverage.py v7.10.1, created at 2025-07-29 05:16 +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''' Data transfer objects for execution context. ''' 

22 

23 

24from . import __ 

25from . import interfaces as _interfaces 

26from . import nomina as _nomina 

27 

28 

29fragments_name_default = '_dynadoc_fragments_' 

30introspection_limit_name_default = '_dynadoc_introspection_limit_' 

31 

32 

33@__.dcls.dataclass( frozen = True, kw_only = True, slots = True ) 

34class Context: 

35 

36 _dynadoc_fragments_: __.typx.ClassVar[ 

37 _interfaces.Fragments ] = ( 'context', ) 

38 

39 notifier: __.typx.Annotated[ 

40 _interfaces.Notifier, 

41 _interfaces.Fname( 'notifier' ), 

42 ] 

43 fragment_rectifier: __.typx.Annotated[ 

44 _interfaces.FragmentRectifier, 

45 _interfaces.Fname( 'fragment rectifier' ), 

46 ] 

47 visibility_decider: __.typx.Annotated[ 

48 _interfaces.VisibilityDecider, 

49 _interfaces.Fname( 'visibility decider' ), 

50 ] 

51 fragments_name: __.typx.Annotated[ 

52 str, 

53 _interfaces.Fname( 'fragments name' ), 

54 ] = fragments_name_default 

55 introspection_limit_name: __.typx.Annotated[ 

56 str, 

57 _interfaces.Fname( 'introspection limit name' ), 

58 ] = introspection_limit_name_default 

59 invoker_globals: __.typx.Annotated[ 

60 __.typx.Optional[ _nomina.Variables ], 

61 _interfaces.Fname( 'invoker globals' ), 

62 ] = None 

63 resolver_globals: __.typx.Annotated[ 

64 __.typx.Optional[ _nomina.Variables ], 

65 _interfaces.Fname( 'resolver globals' ), 

66 ] = None 

67 resolver_locals: __.typx.Annotated[ 

68 __.typx.Optional[ _nomina.Variables ], 

69 _interfaces.Fname( 'resolver locals' ), 

70 ] = None 

71 

72 def with_invoker_globals( 

73 self, 

74 level: _interfaces.GlobalsLevelArgument = 2 

75 ) -> __.typx.Self: 

76 ''' Returns new context with invoker globals from stack frame. ''' 

77 iglobals = __.inspect.stack( )[ level ].frame.f_globals 

78 return type( self )( 

79 notifier = self.notifier, 

80 fragment_rectifier = self.fragment_rectifier, 

81 visibility_decider = self.visibility_decider, 

82 fragments_name = self.fragments_name, 

83 introspection_limit_name = self.introspection_limit_name, 

84 invoker_globals = iglobals, 

85 resolver_globals = self.resolver_globals, 

86 resolver_locals = self.resolver_locals ) 

87 

88 

89ContextArgument: __.typx.TypeAlias = __.typx.Annotated[ 

90 Context, _interfaces.Fname( 'context' ) ] 

91IntrospectionArgumentFref: __.typx.TypeAlias = __.typx.Annotated[ 

92 'IntrospectionControl', _interfaces.Fname( 'introspection' ) ] 

93 

94 

95class ClassIntrospector( __.typx.Protocol ): 

96 ''' Custom introspector for class annotations and attributes. ''' 

97 

98 @staticmethod 

99 def __call__( # noqa: PLR0913 

100 possessor: _interfaces.PossessorClassArgument, /, 

101 context: ContextArgument, 

102 introspection: IntrospectionArgumentFref, 

103 annotations: _interfaces.AnnotationsArgument, 

104 cache: _interfaces.AnnotationsCacheArgument, 

105 table: _interfaces.FragmentsTableArgument, 

106 ) -> __.typx.Optional[ _interfaces.Informations ]: 

107 ''' Introspects class and returns information about its members. ''' 

108 raise NotImplementedError # pragma: no cover 

109 

110ClassIntrospectors: __.typx.TypeAlias = __.cabc.Sequence[ ClassIntrospector ] 

111 

112 

113@__.dcls.dataclass( frozen = True, kw_only = True, slots = True ) 

114class ClassIntrospectionLimit: 

115 ''' Limits on class introspection behavior. ''' 

116 

117 avoid_inheritance: __.typx.Annotated[ 

118 bool, 

119 _interfaces.Doc( ''' Avoid introspecting inherited members? ''' ), 

120 ] = False 

121 ignore_attributes: __.typx.Annotated[ 

122 bool, 

123 _interfaces.Doc( 

124 ''' Ignore attributes not covered by annotations? ''' ), 

125 ] = False 

126 

127 

128@__.dcls.dataclass( frozen = True, kw_only = True, slots = True ) 

129class ClassIntrospectionControl: 

130 ''' Controls on class introspection behavior. ''' 

131 

132 inheritance: __.typx.Annotated[ 

133 bool, _interfaces.Doc( ''' Inherit annotations? ''' ) 

134 ] = False 

135 introspectors: __.typx.Annotated[ 

136 ClassIntrospectors, 

137 _interfaces.Doc( ''' Custom introspectors to apply. ''' ), 

138 ] = ( ) 

139 scan_attributes: __.typx.Annotated[ 

140 bool, 

141 _interfaces.Doc( ''' Scan attributes not covered by annotations? ''' ), 

142 ] = False 

143 

144 def with_limit( 

145 self, 

146 limit: __.typx.Annotated[ 

147 ClassIntrospectionLimit, 

148 _interfaces.Doc( 

149 ''' Limits to apply to this introspection control. ''' ), 

150 ] 

151 ) -> __.typx.Self: 

152 ''' Returns new control with applied limits. ''' 

153 inheritance = self.inheritance and not limit.avoid_inheritance 

154 scan_attributes = self.scan_attributes and not limit.ignore_attributes 

155 return type( self )( 

156 inheritance = inheritance, 

157 introspectors = self.introspectors, 

158 scan_attributes = scan_attributes ) 

159 

160 

161@__.dcls.dataclass( frozen = True, kw_only = True, slots = True ) 

162class ModuleIntrospectionLimit: 

163 ''' Limits on module introspection behavior. ''' 

164 

165 ignore_attributes: __.typx.Annotated[ 

166 bool, 

167 _interfaces.Doc( 

168 ''' Ignore attributes not covered by annotations? ''' ), 

169 ] = False 

170 

171 

172@__.dcls.dataclass( frozen = True, kw_only = True, slots = True ) 

173class ModuleIntrospectionControl: 

174 ''' Controls on module introspection behavior. ''' 

175 

176 scan_attributes: __.typx.Annotated[ 

177 bool, 

178 _interfaces.Doc( ''' Scan attributes not covered by annotations? ''' ), 

179 ] = False 

180 

181 def with_limit( 

182 self, 

183 limit: __.typx.Annotated[ 

184 ModuleIntrospectionLimit, 

185 _interfaces.Doc( 

186 ''' Limits to apply to this introspection control. ''' ), 

187 ] 

188 ) -> __.typx.Self: 

189 ''' Returns new control with applied limits. ''' 

190 scan_attributes = self.scan_attributes and not limit.ignore_attributes 

191 return type( self )( scan_attributes = scan_attributes ) 

192 

193 

194class IntrospectionLimiter( __.typx.Protocol ): 

195 ''' Can return modified introspection control for attribute. ''' 

196 

197 @staticmethod 

198 def __call__( 

199 objct: __.typx.Annotated[ 

200 object, 

201 _interfaces.Doc( 

202 ''' Object being evaluated for introspection limits. ''' ), 

203 ], 

204 introspection: IntrospectionArgumentFref, 

205 ) -> 'IntrospectionControl': 

206 ''' Returns modified introspection control with limits applied. ''' 

207 raise NotImplementedError # pragma: no cover 

208 

209IntrospectionLimiters: __.typx.TypeAlias = __.typx.Annotated[ 

210 __.cabc.Sequence[ IntrospectionLimiter ], 

211 _interfaces.Doc( 

212 ''' Functions which can apply limits to introspection control. ''' ), 

213] 

214 

215 

216class IntrospectionTargets( __.enum.IntFlag ): 

217 ''' Kinds of objects to recursively document. ''' 

218 

219 Null = 0 

220 Class = __.enum.auto( ) 

221 Descriptor = __.enum.auto( ) 

222 Function = __.enum.auto( ) 

223 Module = __.enum.auto( ) 

224 

225 

226IntrospectionTargetsSansModule: __.typx.Annotated[ 

227 IntrospectionTargets, 

228 _interfaces.Doc( ''' All introspection targets except modules. ''' ), 

229] = ( IntrospectionTargets.Class 

230 | IntrospectionTargets.Descriptor 

231 | IntrospectionTargets.Function ) 

232IntrospectionTargetsOmni: __.typx.Annotated[ 

233 IntrospectionTargets, 

234 _interfaces.Doc( 

235 ''' All available introspection targets including modules. ''' ), 

236] = IntrospectionTargetsSansModule | IntrospectionTargets.Module 

237 

238 

239@__.dcls.dataclass( frozen = True, kw_only = True, slots = True ) 

240class IntrospectionLimit: 

241 ''' Limits on introspection behavior. ''' 

242 

243 disable: __.typx.Annotated[ 

244 bool, _interfaces.Doc( ''' Disable introspection? ''' ) 

245 ] = False 

246 class_limit: __.typx.Annotated[ 

247 ClassIntrospectionLimit, 

248 _interfaces.Doc( ''' Limits specific to class introspection. ''' ), 

249 ] = ClassIntrospectionLimit( ) 

250 module_limit: __.typx.Annotated[ 

251 ModuleIntrospectionLimit, 

252 _interfaces.Doc( ''' Limits specific to module introspection. ''' ), 

253 ] = ModuleIntrospectionLimit( ) 

254 targets_exclusions: __.typx.Annotated[ 

255 IntrospectionTargets, 

256 _interfaces.Doc( ''' Target types to exclude from introspection. ''' ), 

257 ] = IntrospectionTargets.Null 

258 

259 

260@__.dcls.dataclass( frozen = True, kw_only = True, slots = True ) 

261class IntrospectionControl: 

262 

263 _dynadoc_fragments_ = ( 'introspection', ) 

264 

265 enable: __.typx.Annotated[ 

266 bool, 

267 _interfaces.Doc( ''' Whether introspection is enabled at all. ''' ), 

268 ] = True 

269 class_control: __.typx.Annotated[ 

270 ClassIntrospectionControl, 

271 _interfaces.Doc( ''' Controls specific to class introspection. ''' ), 

272 ] = ClassIntrospectionControl( ) 

273 module_control: __.typx.Annotated[ 

274 ModuleIntrospectionControl, 

275 _interfaces.Doc( ''' Controls specific to module introspection. ''' ), 

276 ] = ModuleIntrospectionControl( ) 

277 limiters: __.typx.Annotated[ 

278 IntrospectionLimiters, 

279 _interfaces.Doc( 

280 ''' Functions that can apply limits to introspection. ''' ), 

281 ] = ( ) 

282 targets: __.typx.Annotated[ 

283 IntrospectionTargets, 

284 _interfaces.Doc( 

285 ''' Which types of objects to recursively document. ''' ), 

286 ] = IntrospectionTargets.Null 

287 # TODO? Maximum depth. 

288 # (Suggested by multiple LLMs; not convinced that it is needed.) 

289 

290 def evaluate_limits_for( 

291 self, 

292 objct: __.typx.Annotated[ 

293 object, 

294 _interfaces.Doc( ''' Object to evaluate limits for. ''' ), 

295 ] 

296 ) -> 'IntrospectionControl': 

297 ''' Determine which introspection limits apply to object. ''' 

298 introspection_ = self 

299 for limiter in self.limiters: 

300 introspection_ = limiter( objct, introspection_ ) 

301 return introspection_ 

302 

303 def with_limit( 

304 self, 

305 limit: __.typx.Annotated[ 

306 IntrospectionLimit, 

307 _interfaces.Doc( 

308 ''' Limits to apply to this introspection control. ''' ), 

309 ] 

310 ) -> __.typx.Self: 

311 ''' Returns new control with applied limits. ''' 

312 enable = self.enable and not limit.disable 

313 class_control = self.class_control.with_limit( limit.class_limit ) 

314 module_control = self.module_control.with_limit( limit.module_limit ) 

315 targets = self.targets & ~limit.targets_exclusions 

316 return type( self )( 

317 enable = enable, 

318 class_control = class_control, 

319 module_control = module_control, 

320 limiters = self.limiters, 

321 targets = targets ) 

322 

323 

324IntrospectionArgument: __.typx.TypeAlias = __.typx.Annotated[ 

325 IntrospectionControl, _interfaces.Fname( 'introspection' ) ]