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

99 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-07-13 01:33 +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''' Rich annotations, public interfaces for cutomization functions, etc.... 

22 

23 .. note:: 

24 

25 By default, this module exports ``typing_extensions.Doc``, which is 

26 based on the withdrawn :pep:`727`. A ``typing_extensions`` maintainer, 

27 who was also the sponsor of this PEP, has indicated `in the PEP 727 

28 Discourse thread 

29 <https://discuss.python.org/t/pep-727-documentation-metadata-in-typing/32566/183>`_ 

30 that ``typing_extensions`` will support ``Doc`` indefinitely. However, 

31 if it should disappear from ``typing_extensions``, we provide a 

32 compatible fallback. Unless you are using ``typing_extensions.Doc`` for 

33 other purposes, it is recommended that you import it from this package 

34 instead, to ensure future availability. 

35''' 

36 

37 

38from . import __ 

39from . import nomina as _nomina 

40 

41 

42try: from typing_extensions import Doc # pyright: ignore[reportAssignmentType] 

43except ImportError: # pragma: no cover 

44 

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

46 class Doc: 

47 ''' Description of argument or attribute. 

48 

49 Compatible with :pep:`727` ``Doc`` objects. 

50 ''' 

51 

52 documentation: str 

53 

54 

55Fragment: __.typx.TypeAlias = str | Doc 

56Fragments: __.typx.TypeAlias = __.cabc.Sequence[ Fragment ] 

57 

58 

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

60class Fname: 

61 ''' Name of documentation fragment in table. ''' 

62 

63 name: __.typx.Annotated[ 

64 str, Doc( ''' Index to look up content in fragments table. ''' ) ] 

65 

66 

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

68class Raises: 

69 ''' Class and description of exception which can be raised. 

70 

71 Should appear in the return annotations for a function. 

72 ''' 

73 

74 classes: __.typx.Annotated[ 

75 type[ BaseException ] | __.cabc.Sequence[ type[ BaseException ] ], 

76 Doc( ''' Exception class or classes which can be raised. ''' ), 

77 ] 

78 description: __.typx.Annotated[ 

79 __.typx.Optional[ str ], 

80 Doc( ''' When and why the exception is raised. ''' ), 

81 ] = None 

82 

83 

84AnnotationsArgument: __.typx.TypeAlias = __.typx.Annotated[ 

85 _nomina.Annotations, 

86 Doc( ''' Annotations mapping for documentable object. ''' ), 

87] 

88FragmentsTableArgument: __.typx.TypeAlias = __.typx.Annotated[ 

89 _nomina.FragmentsTable, 

90 Doc( ''' Table from which to copy docstring fragments. ''' ), 

91] 

92GlobalsLevelArgument: __.typx.TypeAlias = __.typx.Annotated[ 

93 int, 

94 Doc( 

95 ''' Stack frame level from which to obtain globals. 

96 

97 Default is 2, which is the caller of the caller. 

98 ''' ), 

99] 

100NotifierLevelArgument: __.typx.TypeAlias = __.typx.Annotated[ 

101 _nomina.NotificationLevels, 

102 Doc( ''' Severity level of the notification. ''' ), 

103] 

104NotifierMessageArgument: __.typx.TypeAlias = __.typx.Annotated[ 

105 str, 

106 Doc( ''' Message content to notify about. ''' ), 

107] 

108PossessorArgument: __.typx.TypeAlias = __.typx.Annotated[ 

109 _nomina.Documentable, 

110 Doc( 

111 ''' Object being documented. 

112 

113 May be a module, class, or function. 

114 ''' ), 

115] 

116PossessorClassArgument: __.typx.TypeAlias = __.typx.Annotated[ 

117 type, Doc( ''' Class being documented. ''' ) ] 

118PossessorFunctionArgument: __.typx.TypeAlias = __.typx.Annotated[ 

119 __.cabc.Callable[ ..., __.typx.Any ], 

120 Doc( ''' Function being documented. ''' ), 

121] 

122PossessorModuleArgument: __.typx.TypeAlias = __.typx.Annotated[ 

123 __.types.ModuleType, Doc( ''' Module being documented. ''' ) ] 

124VisibilityAnnotationArgument: __.typx.TypeAlias = __.typx.Annotated[ 

125 __.typx.Any, Doc( ''' Type annotation of the attribute. ''' ) ] 

126VisibilityDescriptionArgument: __.typx.TypeAlias = __.typx.Annotated[ 

127 __.typx.Optional[ str ], 

128 Doc( ''' Optional description text for the attribute. ''' ), 

129] 

130VisibilityNameArgument: __.typx.TypeAlias = __.typx.Annotated[ 

131 str, Doc( ''' Name of the attribute being evaluated. ''' ) ] 

132 

133 

134class Sentinels( __.enum.Enum ): 

135 ''' Sentinel values used in various parts of the package. ''' 

136 

137 Absent = __.enum.auto( ) 

138 Incomplete = __.enum.auto( ) 

139 

140 

141absent: __.typx.Annotated[ 

142 Sentinels, Doc( ''' Indicates annotation or other data is missing. ''' ) 

143] = Sentinels.Absent 

144incomplete: __.typx.Annotated[ 

145 Sentinels, Doc( ''' Indicates annotation reduction is incomplete. ''' ), 

146] = Sentinels.Incomplete 

147 

148 

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

150class AdjunctsData: 

151 ''' Data about type-adjacent entities. ''' 

152 

153 extras: __.typx.Annotated[ 

154 __.cabc.MutableSequence[ __.typx.Any ], 

155 Doc( ''' Additional annotations. ''' ), 

156 ] = __.dcls.field( default_factory = list[ __.typx.Any ] ) 

157 traits: __.typx.Annotated[ 

158 __.cabc.MutableSet[ str ], 

159 Doc( ''' Trait names collected during annotation processing. ''' ), 

160 ] = __.dcls.field( default_factory = set[ str ] ) 

161 

162 def copy( self ) -> __.typx.Self: 

163 ''' Creates a shallow copy of the adjuncts data. ''' 

164 return type( self )( 

165 extras = list( self.extras ), 

166 traits = set( self.traits ) ) 

167 

168 

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

170class AnnotationsCache: 

171 ''' Lookup table for reduced annotations from original annotations. 

172 

173 Has special values for absent and incomplete entries. 

174 ''' 

175 

176 entries: __.typx.Annotated[ 

177 dict[ __.typx.Any, __.typx.Any ], 

178 Doc( ''' Mapping from original annotations to reduced forms. ''' ), 

179 ] = __.dcls.field( default_factory = dict[ __.typx.Any, __.typx.Any ] ) 

180 

181 def access( 

182 self, original: __.typx.Annotated[ 

183 __.typx.Any, 

184 Doc( ''' Original annotation to look up in cache. ''' ), 

185 ] 

186 ) -> __.typx.Annotated[ 

187 __.typx.Any, 

188 Doc( 

189 ''' Reduced annotation from cache. 

190 

191 Absence sentinel if not found. 

192 ''' ), 

193 ]: 

194 ''' Accesses entry value, if it exists. ''' 

195 try: return self.entries.get( original, absent ) 

196 except TypeError: return self.entries.get( id( original ), absent ) 

197 

198 def enter( 

199 self, 

200 original: __.typx.Annotated[ 

201 __.typx.Any, 

202 Doc( ''' Original annotation to use as cache key. ''' ), 

203 ], 

204 reduction: __.typx.Annotated[ 

205 __.typx.Any, 

206 Doc( ''' Reduced form of annotation to store as value. ''' ), 

207 ] = incomplete, 

208 ) -> __.typx.Any: 

209 ''' Adds reduced annotation to cache, returning it. 

210 

211 Cache key is original annotation. 

212 If reduction is not specified, then an incompletion sentinel is 

213 added as the value for the entry. 

214 ''' 

215 try: self.entries[ original ] = reduction 

216 except TypeError: self.entries[ id( original ) ] = reduction 

217 return reduction 

218 

219 

220class AttributeAssociations( __.enum.Enum ): 

221 ''' Association level of an attribute with its containing entity. ''' 

222 

223 Module = __.enum.auto( ) 

224 Class = __.enum.auto( ) 

225 Instance = __.enum.auto( ) 

226 

227 

228class ValuationModes( __.enum.Enum ): 

229 ''' Annotation for how default value is determined. 

230 

231 Accept means to use assigned value. 

232 Suppress means to use no value. 

233 Surrogate means to use surrogate value. 

234 ''' 

235 

236 Accept = __.enum.auto( ) 

237 Suppress = __.enum.auto( ) 

238 Surrogate = __.enum.auto( ) 

239 

240 

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

242class Default: 

243 ''' How to process default value. ''' 

244 

245 mode: __.typx.Annotated[ 

246 ValuationModes, 

247 Doc( ''' Method for handling default value processing. ''' ), 

248 ] = ValuationModes.Accept 

249 surrogate: __.typx.Annotated[ 

250 __.typx.Any, 

251 Doc( 

252 ''' Alternative value to use when surrogate mode. 

253 

254 Usually a description string. 

255 ''' ), 

256 ] = absent 

257 

258 

259class FragmentSources( __.enum.Enum ): 

260 ''' Possible sources for documentation fragments. ''' 

261 

262 Annotation = __.enum.auto( ) 

263 Argument = __.enum.auto( ) # *fragments 

264 Attribute = __.enum.auto( ) # _dynadoc_fragments_ 

265 Docstring = __.enum.auto( ) 

266 Renderer = __.enum.auto( ) 

267 

268 

269class FragmentRectifier( __.typx.Protocol ): 

270 ''' Cleans and normalizes documentation fragment. ''' 

271 

272 @staticmethod 

273 def __call__( 

274 fragment: __.typx.Annotated[ 

275 str, 

276 Doc( ''' Raw fragment text to be cleaned and normalized. ''' ), 

277 ], 

278 source: __.typx.Annotated[ 

279 FragmentSources, 

280 Doc( 

281 ''' Source type of fragment for context-aware processing. 

282 ''' ), 

283 ], 

284 ) -> str: 

285 ''' (Signature for fragment rectifier.) ''' 

286 raise NotImplementedError # pragma: no cover 

287 

288 

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

290class InformationBase: 

291 ''' Base for information on various kinds of entities. ''' 

292 

293 annotation: __.typx.Annotated[ 

294 __.typx.Any, 

295 Doc( ''' Type annotation associated with this entity. ''' ), 

296 ] 

297 description: __.typx.Annotated[ 

298 __.typx.Optional[ str ], 

299 Doc( ''' Human-readable description of the entity. ''' ), 

300 ] 

301 

302 

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

304class ArgumentInformation( InformationBase ): 

305 ''' Information about a function argument. ''' 

306 

307 name: __.typx.Annotated[ 

308 str, 

309 Doc( ''' Name of the function parameter. ''' ), 

310 ] 

311 paramspec: __.typx.Annotated[ 

312 __.inspect.Parameter, 

313 Doc( ''' Inspection parameter object with various details. ''' ), 

314 ] 

315 default: __.typx.Annotated[ 

316 Default, 

317 Doc( ''' Configuration for how to handle default value. ''' ), 

318 ] 

319 

320 

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

322class AttributeInformation( InformationBase ): 

323 ''' Information about a class or module attribute. ''' 

324 

325 name: __.typx.Annotated[ 

326 str, 

327 Doc( ''' Name of the attribute. ''' ), 

328 ] 

329 association: __.typx.Annotated[ 

330 AttributeAssociations, 

331 Doc( ''' Attribute associated with module, class, or instance? ''' ), 

332 ] 

333 default: __.typx.Annotated[ 

334 Default, Doc( ''' How to handle default value. ''' ) ] 

335 

336 

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

338class ExceptionInformation( InformationBase ): 

339 ''' Information about an exception that can be raised. ''' 

340 pass 

341 

342 

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

344class ReturnInformation( InformationBase ): 

345 ''' Information about a function's return value. ''' 

346 pass 

347 

348 

349Informations: __.typx.TypeAlias = __.cabc.Sequence[ InformationBase ] 

350 

351 

352class Notifier( __.typx.Protocol ): 

353 ''' Notifies of warnings and errors. ''' 

354 

355 @staticmethod 

356 def __call__( 

357 level: NotifierLevelArgument, 

358 message: NotifierMessageArgument, 

359 ) -> None: 

360 ''' (Signature for notifier callback.) ''' 

361 raise NotImplementedError # pragma: no cover 

362 

363 

364class Visibilities( __.enum.Enum ): 

365 ''' Annotation to determine visibility of attribute. 

366 

367 Default means to defer to visibility predicate in use. 

368 Conceal means to hide regardless of visibility predicate. 

369 Reveal means to show regardless of visibility predicate. 

370 ''' 

371 

372 Default = __.enum.auto( ) 

373 Conceal = __.enum.auto( ) 

374 Reveal = __.enum.auto( ) 

375 

376 

377class VisibilityDecider( __.typx.Protocol ): 

378 ''' Decides if attribute should have visible documentation. ''' 

379 

380 @staticmethod 

381 def __call__( 

382 possessor: PossessorArgument, 

383 name: VisibilityNameArgument, 

384 annotation: VisibilityAnnotationArgument, 

385 description: VisibilityDescriptionArgument, 

386 ) -> bool: 

387 ''' (Signature for visibility decider.) ''' 

388 raise NotImplementedError # pragma: no cover 

389 

390 

391AnnotationsCacheArgument: __.typx.TypeAlias = __.typx.Annotated[ 

392 AnnotationsCache, 

393 Doc( 

394 ''' Cache for storing reduced annotation forms. 

395 

396 Also used for cycle detection. 

397 ''' ), 

398] 

399InformationsArgument: __.typx.TypeAlias = __.typx.Annotated[ 

400 Informations, 

401 Doc( 

402 ''' Sequence of information blocks from object introspection. 

403 

404 Information may be about arguments to a function, attributes on a 

405 class or module, exceptions raised by a function, or returns from a 

406 function. 

407 ''' ), 

408]