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
« prev ^ index » next coverage.py v7.6.10, created at 2025-02-18 01:26 +0100
1"""This module defines the Griffe TypingDoc extension."""
3from __future__ import annotations
5from typing import TYPE_CHECKING, Any
7from griffe import Docstring, Extension, Function, ObjectNode
9from griffe_typingdoc import _dynamic, _static
11if TYPE_CHECKING:
12 import ast
13 from typing import Annotated
15 from griffe.dataclasses import Attribute, Module, Object
16 from typing_extensions import Doc
19class TypingDocExtension(Extension):
20 """Griffe extension that reads documentation from `typing.Doc`."""
22 def __init__(self) -> None:
23 self._handled: set[str] = set()
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)
30 module = _dynamic if node else _static
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 )
39 if not any(new_sections):
40 return
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)
45 sections = attr.docstring.parsed
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)
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)
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)
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)
61 module = _dynamic if node else _static
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 )
74 if not any(new_sections):
75 return
77 if not func.docstring:
78 func.docstring = Docstring("", parent=func)
80 sections = func.docstring.parsed
82 if other_params_section:
83 sections.insert(1, other_params_section)
85 if params_section:
86 sections.insert(1, params_section)
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)
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)
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)
97 if yields_section:
98 sections.append(yields_section)
100 if receives_section:
101 sections.append(receives_section)
103 if returns_section:
104 sections.append(returns_section)
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)
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)
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.
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)
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.
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)