Coverage for src/pytkdocs/parsers/docstrings/base.py: 85.71%

88 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-03-09 17:28 +0100

1"""The base module for docstring parsing.""" 

2 

3import inspect 

4from abc import ABCMeta, abstractmethod 

5from typing import Any, Optional 

6 

7empty = inspect.Signature.empty 

8 

9 

10class AnnotatedObject: 

11 """A helper class to store information about an annotated object.""" 

12 

13 def __init__(self, annotation: Any, description: str) -> None: 

14 """Initialize the object. 

15 

16 Arguments: 

17 annotation: The object's annotation. 

18 description: The object's description. 

19 """ 

20 self.annotation = annotation 

21 self.description = description 

22 

23 

24class Attribute(AnnotatedObject): 

25 """A helper class to store information about a documented attribute.""" 

26 

27 def __init__(self, name: str, annotation: Any, description: str) -> None: 

28 """Initialize the object. 

29 

30 Arguments: 

31 name: The attribute's name. 

32 annotation: The object's annotation. 

33 description: The object's description. 

34 """ 

35 super().__init__(annotation, description) 

36 self.name = name 

37 

38 

39class Parameter(AnnotatedObject): 

40 """A helper class to store information about a signature parameter.""" 

41 

42 def __init__(self, name: str, annotation: Any, description: str, kind: Any, default: Any = empty) -> None: 

43 """Initialize the object. 

44 

45 Arguments: 

46 name: The parameter's name. 

47 annotation: The parameter's annotation. 

48 description: The parameter's description. 

49 kind: The parameter's kind (positional only, keyword only, etc.). 

50 default: The parameter's default value. 

51 """ 

52 super().__init__(annotation, description) 

53 self.name = name 

54 self.kind = kind 

55 self.default = default 

56 

57 def __str__(self): 

58 return self.name 

59 

60 def __repr__(self): 

61 return f"<Parameter({self.name}, {self.annotation}, {self.description}, {self.kind}, {self.default})>" 

62 

63 @property 

64 def is_optional(self) -> bool: 

65 """Tell if this parameter is optional.""" 

66 return self.default is not empty 

67 

68 @property 

69 def is_required(self) -> bool: 

70 """Tell if this parameter is required.""" 

71 return not self.is_optional 

72 

73 @property 

74 def is_args(self) -> bool: 

75 """Tell if this parameter is positional.""" 

76 return self.kind is inspect.Parameter.VAR_POSITIONAL 

77 

78 @property 

79 def is_kwargs(self) -> bool: 

80 """Tell if this parameter is a keyword.""" 

81 return self.kind is inspect.Parameter.VAR_KEYWORD 

82 

83 @property 

84 def default_string(self) -> str: 

85 """Return the default value as a string.""" 

86 if self.is_kwargs: 

87 return "{}" 

88 if self.is_args: 

89 return "()" 

90 if self.is_required: 

91 return "" 

92 return repr(self.default) 

93 

94 

95class Section: 

96 """A helper class to store a docstring section.""" 

97 

98 class Type: 

99 """The possible section types.""" 

100 

101 MARKDOWN = "markdown" 

102 PARAMETERS = "parameters" 

103 EXCEPTIONS = "exceptions" 

104 RETURN = "return" 

105 YIELD = "yield" 

106 EXAMPLES = "examples" 

107 ATTRIBUTES = "attributes" 

108 KEYWORD_ARGS = "keyword_args" 

109 

110 def __init__(self, section_type: str, value: Any) -> None: 

111 """Initialize the object. 

112 

113 Arguments: 

114 section_type: The type of the section, from the [`Type`][pytkdocs.parsers.docstrings.base.Section.Type] enum. 

115 value: The section value. 

116 """ 

117 self.type = section_type 

118 self.value = value 

119 

120 def __str__(self): 

121 return self.type 

122 

123 def __repr__(self): 

124 return f"<Section(type={self.type!r})>" 

125 

126 

127class Parser(metaclass=ABCMeta): 

128 """A class to parse docstrings. 

129 

130 It is instantiated with an object's path, docstring, signature and return type. 

131 

132 The `parse` method then returns structured data, 

133 in the form of a list of [`Section`][pytkdocs.parsers.docstrings.base.Section]s. 

134 It also return the list of errors that occurred during parsing. 

135 """ 

136 

137 def __init__(self, **kwargs: Any) -> None: # noqa: ARG002 

138 """Initialize the object.""" 

139 self.context: dict = {} 

140 self.errors: list[str] = [] 

141 

142 def parse(self, docstring: str, context: Optional[dict] = None) -> tuple[list[Section], list[str]]: 

143 """Parse a docstring and return a list of sections and parsing errors. 

144 

145 Arguments: 

146 docstring: The docstring to parse. 

147 context: Some context helping to parse the docstring. 

148 

149 Returns: 

150 A tuple containing the list of sections and the parsing errors. 

151 """ 

152 self.context = context or {} 

153 self.errors = [] 

154 sections = self.parse_sections(docstring) 

155 errors = self.errors 

156 return sections, errors 

157 

158 def error(self, message: str) -> None: 

159 """Record a parsing error. 

160 

161 Arguments: 

162 message: A message described the error. 

163 """ 

164 if self.context["obj"]: 164 ↛ 166line 164 didn't jump to line 166 because the condition on line 164 was always true

165 message = f"{self.context['obj'].path}: {message}" 

166 self.errors.append(message) 

167 

168 @abstractmethod 

169 def parse_sections(self, docstring: str) -> list[Section]: 

170 """Parse a docstring as a list of sections. 

171 

172 Arguments: 

173 docstring: The docstring to parse. 

174 

175 Returns: 

176 A list of [`Section`][pytkdocs.parsers.docstrings.base.Section]s. 

177 """ 

178 raise NotImplementedError 

179 

180 

181class UnavailableParser: # noqa: D101 

182 def __init__(self, message: str) -> None: # noqa: D107 

183 self.message = message 

184 

185 def parse(self, docstring: str, context: Optional[dict] = None) -> tuple[list[Section], list[str]]: # noqa: ARG002, D102 

186 context = context or {} 

187 message = self.message 

188 if "obj" in context: 

189 message = f"{context['obj'].path}: {message}" 

190 return [], [message] 

191 

192 def __call__(self, *args, **kwargs): # noqa: ANN003, ANN002, ARG002, D102 

193 return self