Coverage for src/pytkdocs/serializer.py: 92.52%
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2This module defines function to serialize objects.
4These functions simply take objects as parameters and return dictionaries that can be dumped by `json.dumps`.
5"""
7import inspect
8import re
9from typing import Any, Match, Optional, Pattern
11from pytkdocs.objects import Object, Source
12from pytkdocs.parsers.docstrings.base import AnnotatedObject, Attribute, Parameter, Section
14try:
15 from typing import GenericMeta # type: ignore
16except ImportError:
17 # in 3.7, GenericMeta doesn't exist but we don't need it
18 class GenericMeta(type): # type: ignore # noqa: WPS440 (variable overlap)
19 """GenericMeta type."""
22RE_OPTIONAL: Pattern = re.compile(r"Union\[(.+), NoneType\]")
23"""Regular expression to match optional annotations of the form `Union[T, NoneType]`."""
25RE_FORWARD_REF: Pattern = re.compile(r"_?ForwardRef\('([^']+)'\)")
26"""Regular expression to match forward-reference annotations of the form `_ForwardRef('T')`."""
29def rebuild_optional(match: Match) -> str:
30 """
31 Rebuild `Union[T, None]` as `Optional[T]`.
33 Arguments:
34 match: The match object when matching against a regular expression (by the parent caller).
36 Returns:
37 The rebuilt type string.
38 """
39 group = match.group(1)
40 brackets_level = 0
41 for char in group:
42 if char == "," and brackets_level == 0:
43 return f"Union[{group}]"
44 if char == "[":
45 brackets_level += 1
46 elif char == "]":
47 brackets_level -= 1
48 return f"Optional[{group}]"
51def annotation_to_string(annotation: Any) -> str:
52 """
53 Return an annotation as a string.
55 Arguments:
56 annotation: The annotation to return as a string.
58 Returns:
59 The annotation as a string.
60 """
61 if annotation is inspect.Signature.empty:
62 return ""
64 if inspect.isclass(annotation) and not isinstance(annotation, GenericMeta):
65 string = annotation.__name__
66 else:
67 string = str(annotation).replace("typing.", "")
69 string = RE_FORWARD_REF.sub(lambda match: match.group(1), string)
70 string = RE_OPTIONAL.sub(rebuild_optional, string)
72 return string # noqa: WPS331 (false-positive, string is not only used for the return)
75def serialize_annotated_object(obj: AnnotatedObject) -> dict:
76 """
77 Serialize an instance of [`AnnotatedObject`][pytkdocs.parsers.docstrings.base.AnnotatedObject].
79 Arguments:
80 obj: The object to serialize.
82 Returns:
83 A JSON-serializable dictionary.
84 """
85 return {"description": obj.description, "annotation": annotation_to_string(obj.annotation)}
88def serialize_attribute(attribute: Attribute) -> dict:
89 """
90 Serialize an instance of [`Attribute`][pytkdocs.parsers.docstrings.base.Attribute].
92 Arguments:
93 attribute: The attribute to serialize.
95 Returns:
96 A JSON-serializable dictionary.
97 """
98 return {
99 "name": attribute.name,
100 "description": attribute.description,
101 "annotation": annotation_to_string(attribute.annotation),
102 }
105def serialize_parameter(parameter: Parameter) -> dict:
106 """
107 Serialize an instance of [`Parameter`][pytkdocs.parsers.docstrings.base.Parameter].
109 Arguments:
110 parameter: The parameter to serialize.
112 Returns:
113 A JSON-serializable dictionary.
114 """
115 serialized = serialize_annotated_object(parameter)
116 serialized.update(
117 {
118 "name": parameter.name,
119 "kind": str(parameter.kind),
120 "default": parameter.default_string,
121 "is_optional": parameter.is_optional,
122 "is_required": parameter.is_required,
123 "is_args": parameter.is_args,
124 "is_kwargs": parameter.is_kwargs,
125 },
126 )
127 return serialized
130def serialize_signature_parameter(parameter: inspect.Parameter) -> dict:
131 """
132 Serialize an instance of `inspect.Parameter`.
134 Arguments:
135 parameter: The parameter to serialize.
137 Returns:
138 A JSON-serializable dictionary.
139 """
140 serialized = {"kind": str(parameter.kind), "name": parameter.name}
141 if parameter.annotation is not parameter.empty:
142 serialized["annotation"] = annotation_to_string(parameter.annotation)
143 if parameter.default is not parameter.empty:
144 serialized["default"] = repr(parameter.default)
145 return serialized
148def serialize_signature(signature: inspect.Signature) -> dict:
149 """
150 Serialize an instance of `inspect.Signature`.
152 Arguments:
153 signature: The signature to serialize.
155 Returns:
156 A JSON-serializable dictionary.
157 """
158 if signature is None: 158 ↛ 159line 158 didn't jump to line 159, because the condition on line 158 was never true
159 return {}
160 serialized: dict = {
161 "parameters": [serialize_signature_parameter(value) for name, value in signature.parameters.items()],
162 }
163 if signature.return_annotation is not inspect.Signature.empty:
164 serialized["return_annotation"] = annotation_to_string(signature.return_annotation)
165 return serialized
168def serialize_docstring_section(section: Section) -> dict: # noqa: WPS231 (not complex)
169 """
170 Serialize an instance of `inspect.Signature`.
172 Arguments:
173 section: The section to serialize.
175 Returns:
176 A JSON-serializable dictionary.
177 """
178 serialized = {"type": section.type}
179 if section.type == section.Type.MARKDOWN:
180 serialized.update({"value": section.value})
181 elif section.type == section.Type.RETURN:
182 serialized.update({"value": serialize_annotated_object(section.value)}) # type: ignore
183 elif section.type == section.Type.YIELD:
184 serialized.update({"value": serialize_annotated_object(section.value)}) # type: ignore
185 elif section.type == section.Type.EXCEPTIONS:
186 serialized.update({"value": [serialize_annotated_object(exc) for exc in section.value]}) # type: ignore
187 elif section.type == section.Type.PARAMETERS:
188 serialized.update({"value": [serialize_parameter(param) for param in section.value]}) # type: ignore
189 elif section.type == section.Type.KEYWORD_ARGS: 189 ↛ 190line 189 didn't jump to line 190, because the condition on line 189 was never true
190 serialized.update({"value": [serialize_parameter(param) for param in section.value]}) # type: ignore
191 elif section.type == section.Type.ATTRIBUTES: 191 ↛ 193line 191 didn't jump to line 193, because the condition on line 191 was never false
192 serialized.update({"value": [serialize_attribute(attr) for attr in section.value]}) # type: ignore
193 elif section.type == section.Type.EXAMPLES:
194 serialized.update({"value": section.value})
195 return serialized
198def serialize_source(source: Optional[Source]) -> dict:
199 """
200 Serialize an instance of [`Source`][pytkdocs.objects.Source].
202 Arguments:
203 source: The source to serialize.
205 Returns:
206 A JSON-serializable dictionary.
207 """
208 if source:
209 return {"code": source.code, "line_start": source.line_start}
210 return {}
213def serialize_object(obj: Object) -> dict:
214 """
215 Serialize an instance of a subclass of [`Object`][pytkdocs.objects.Object].
217 Arguments:
218 obj: The object to serialize.
220 Returns:
221 A JSON-serializable dictionary.
222 """
223 serialized = {
224 "name": obj.name,
225 "path": obj.path,
226 "category": obj.category,
227 "file_path": obj.file_path,
228 "relative_file_path": obj.relative_file_path,
229 "properties": sorted(set(obj.properties + obj.name_properties)),
230 "parent_path": obj.parent_path,
231 "has_contents": obj.has_contents(),
232 "docstring": obj.docstring,
233 "docstring_sections": [serialize_docstring_section(sec) for sec in obj.docstring_sections],
234 "source": serialize_source(obj.source),
235 "children": {child.path: serialize_object(child) for child in obj.children},
236 "attributes": [attr.path for attr in obj.attributes],
237 "methods": [meth.path for meth in obj.methods],
238 "functions": [func.path for func in obj.functions],
239 "modules": [mod.path for mod in obj.modules],
240 "classes": [clas.path for clas in obj.classes],
241 }
242 if hasattr(obj, "type"): # noqa: WPS421 (hasattr)
243 serialized["type"] = annotation_to_string(obj.type) # type: ignore
244 if hasattr(obj, "signature"): # noqa: WPS421 (hasattr)
245 serialized["signature"] = serialize_signature(obj.signature) # type: ignore
246 if hasattr(obj, "bases"): # noqa: WPS421 (hasattr)
247 serialized["bases"] = obj.bases # type: ignore
248 return serialized