Coverage for src/griffe_typingdoc/_extension.py: 81.20%

69 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-02-18 01:26 +0100

1"""This module defines the Griffe TypingDoc extension.""" 

2 

3from __future__ import annotations 

4 

5from typing import TYPE_CHECKING, Any 

6 

7from griffe import Docstring, Extension, Function, ObjectNode 

8 

9from griffe_typingdoc import _dynamic, _static 

10 

11if TYPE_CHECKING: 

12 import ast 

13 from typing import Annotated 

14 

15 from griffe.dataclasses import Attribute, Module, Object 

16 from typing_extensions import Doc 

17 

18 

19class TypingDocExtension(Extension): 

20 """Griffe extension that reads documentation from `typing.Doc`.""" 

21 

22 def __init__(self) -> None: 

23 self._handled: set[str] = set() 

24 

25 def _handle_attribute(self, attr: Attribute, /, *, node: ObjectNode | None = None) -> None: 

26 if attr.path in self._handled: 26 ↛ 27line 26 didn't jump to line 27 because the condition on line 26 was never true

27 return 

28 self._handled.add(attr.path) 

29 

30 module = _dynamic if node else _static 

31 

32 new_sections = ( 

33 docstring := module._attribute_docs(attr, node=node), 

34 deprecated_section := module._deprecated_docs(attr, node=node), 

35 raises_section := module._raises_docs(attr, node=node), 

36 warns_section := module._warns_docs(attr, node=node), 

37 ) 

38 

39 if not any(new_sections): 

40 return 

41 

42 if not attr.docstring: 42 ↛ 45line 42 didn't jump to line 45 because the condition on line 42 was always true

43 attr.docstring = Docstring(docstring, parent=attr) 

44 

45 sections = attr.docstring.parsed 

46 

47 if deprecated_section: 47 ↛ 48line 47 didn't jump to line 48 because the condition on line 47 was never true

48 sections.insert(0, deprecated_section) 

49 

50 if raises_section: 50 ↛ 51line 50 didn't jump to line 51 because the condition on line 50 was never true

51 sections.append(raises_section) 

52 

53 if warns_section: 53 ↛ 54line 53 didn't jump to line 54 because the condition on line 53 was never true

54 sections.append(warns_section) 

55 

56 def _handle_function(self, func: Function, /, *, node: ObjectNode | None = None) -> None: 

57 if func.path in self._handled: 57 ↛ 58line 57 didn't jump to line 58 because the condition on line 57 was never true

58 return 

59 self._handled.add(func.path) 

60 

61 module = _dynamic if node else _static 

62 

63 new_sections = ( 

64 deprecated_section := module._deprecated_docs(func, node=node), 

65 params_section := module._parameters_docs(func, node=node), 

66 other_params_section := module._other_parameters_docs(func, node=node), 

67 warns_section := module._warns_docs(func, node=node), 

68 raises_section := module._raises_docs(func, node=node), 

69 yields_section := module._yields_docs(func, node=node), 

70 receives_section := module._receives_docs(func, node=node), 

71 returns_section := module._returns_docs(func, node=node), 

72 ) 

73 

74 if not any(new_sections): 

75 return 

76 

77 if not func.docstring: 

78 func.docstring = Docstring("", parent=func) 

79 

80 sections = func.docstring.parsed 

81 

82 if other_params_section: 

83 sections.insert(1, other_params_section) 

84 

85 if params_section: 

86 sections.insert(1, params_section) 

87 

88 if deprecated_section: 88 ↛ 89line 88 didn't jump to line 89 because the condition on line 88 was never true

89 sections.insert(0, deprecated_section) 

90 

91 if raises_section: 91 ↛ 92line 91 didn't jump to line 92 because the condition on line 91 was never true

92 sections.append(raises_section) 

93 

94 if warns_section: 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true

95 sections.append(warns_section) 

96 

97 if yields_section: 

98 sections.append(yields_section) 

99 

100 if receives_section: 

101 sections.append(receives_section) 

102 

103 if returns_section: 

104 sections.append(returns_section) 

105 

106 def _handle_object(self, obj: Object) -> None: 

107 if obj.is_alias: 

108 return 

109 if obj.is_module or obj.is_class: 

110 for member in obj.members.values(): 

111 self._handle_object(member) 

112 elif obj.is_function: 

113 self._handle_function(obj) 

114 elif obj.is_attribute: 114 ↛ exitline 114 didn't return from function '_handle_object' because the condition on line 114 was always true

115 self._handle_attribute(obj) 

116 

117 def on_package_loaded( 

118 self, 

119 *, 

120 pkg: Annotated[ 

121 Module, 

122 Doc("The top-level module representing a package."), 

123 ], 

124 **kwargs: Any, # noqa: ARG002 

125 ) -> None: 

126 """Post-process Griffe packages recursively (non-yet handled objects only).""" 

127 self._handle_object(pkg) 

128 

129 def on_function_instance( 

130 self, 

131 *, 

132 node: Annotated[ 

133 ast.AST | ObjectNode, 

134 Doc("The object/AST node describing the function or its definition."), 

135 ], 

136 func: Annotated[ 

137 Function, 

138 Doc("""The Griffe function just instantiated."""), 

139 ], 

140 **kwargs: Any, # noqa: ARG002 

141 ) -> None: 

142 """Post-process Griffe functions to add a parameters section. 

143 

144 It applies only for dynamic analysis. 

145 """ 

146 if isinstance(node, ObjectNode): 146 ↛ 147line 146 didn't jump to line 147 because the condition on line 146 was never true

147 self._handle_function(func, node=node) 

148 

149 def on_attribute_instance( 

150 self, 

151 *, 

152 node: Annotated[ 

153 ast.AST | ObjectNode, 

154 Doc("The object/AST node describing the attribute or its definition."), 

155 ], 

156 attr: Annotated[ 

157 Attribute, 

158 Doc("The Griffe attribute just instantiated."), 

159 ], 

160 **kwargs: Any, # noqa: ARG002 

161 ) -> None: 

162 """Post-process Griffe attributes to create their docstring. 

163 

164 It applies only for dynamic analysis. 

165 """ 

166 if isinstance(node, ObjectNode): 166 ↛ 167line 166 didn't jump to line 167 because the condition on line 166 was never true

167 self._handle_attribute(attr, node=node)