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
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-09 17:28 +0100
1"""The base module for docstring parsing."""
3import inspect
4from abc import ABCMeta, abstractmethod
5from typing import Any, Optional
7empty = inspect.Signature.empty
10class AnnotatedObject:
11 """A helper class to store information about an annotated object."""
13 def __init__(self, annotation: Any, description: str) -> None:
14 """Initialize the object.
16 Arguments:
17 annotation: The object's annotation.
18 description: The object's description.
19 """
20 self.annotation = annotation
21 self.description = description
24class Attribute(AnnotatedObject):
25 """A helper class to store information about a documented attribute."""
27 def __init__(self, name: str, annotation: Any, description: str) -> None:
28 """Initialize the object.
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
39class Parameter(AnnotatedObject):
40 """A helper class to store information about a signature parameter."""
42 def __init__(self, name: str, annotation: Any, description: str, kind: Any, default: Any = empty) -> None:
43 """Initialize the object.
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
57 def __str__(self):
58 return self.name
60 def __repr__(self):
61 return f"<Parameter({self.name}, {self.annotation}, {self.description}, {self.kind}, {self.default})>"
63 @property
64 def is_optional(self) -> bool:
65 """Tell if this parameter is optional."""
66 return self.default is not empty
68 @property
69 def is_required(self) -> bool:
70 """Tell if this parameter is required."""
71 return not self.is_optional
73 @property
74 def is_args(self) -> bool:
75 """Tell if this parameter is positional."""
76 return self.kind is inspect.Parameter.VAR_POSITIONAL
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
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)
95class Section:
96 """A helper class to store a docstring section."""
98 class Type:
99 """The possible section types."""
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"
110 def __init__(self, section_type: str, value: Any) -> None:
111 """Initialize the object.
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
120 def __str__(self):
121 return self.type
123 def __repr__(self):
124 return f"<Section(type={self.type!r})>"
127class Parser(metaclass=ABCMeta):
128 """A class to parse docstrings.
130 It is instantiated with an object's path, docstring, signature and return type.
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 """
137 def __init__(self, **kwargs: Any) -> None: # noqa: ARG002
138 """Initialize the object."""
139 self.context: dict = {}
140 self.errors: list[str] = []
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.
145 Arguments:
146 docstring: The docstring to parse.
147 context: Some context helping to parse the docstring.
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
158 def error(self, message: str) -> None:
159 """Record a parsing error.
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)
168 @abstractmethod
169 def parse_sections(self, docstring: str) -> list[Section]:
170 """Parse a docstring as a list of sections.
172 Arguments:
173 docstring: The docstring to parse.
175 Returns:
176 A list of [`Section`][pytkdocs.parsers.docstrings.base.Section]s.
177 """
178 raise NotImplementedError
181class UnavailableParser: # noqa: D101
182 def __init__(self, message: str) -> None: # noqa: D107
183 self.message = message
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]
192 def __call__(self, *args, **kwargs): # noqa: ANN003, ANN002, ARG002, D102
193 return self