sphinx-autoapi 3.3.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- autoapi/__init__.py +7 -0
- autoapi/_astroid_utils.py +684 -0
- autoapi/_mapper.py +641 -0
- autoapi/_objects.py +536 -0
- autoapi/_parser.py +418 -0
- autoapi/directives.py +67 -0
- autoapi/documenters.py +315 -0
- autoapi/extension.py +283 -0
- autoapi/inheritance_diagrams.py +131 -0
- autoapi/settings.py +23 -0
- autoapi/templates/index.rst +13 -0
- autoapi/templates/python/attribute.rst +1 -0
- autoapi/templates/python/class.rst +104 -0
- autoapi/templates/python/data.rst +38 -0
- autoapi/templates/python/exception.rst +1 -0
- autoapi/templates/python/function.rst +21 -0
- autoapi/templates/python/method.rst +21 -0
- autoapi/templates/python/module.rst +156 -0
- autoapi/templates/python/package.rst +1 -0
- autoapi/templates/python/property.rst +21 -0
- sphinx_autoapi-3.3.3.dist-info/METADATA +153 -0
- sphinx_autoapi-3.3.3.dist-info/RECORD +23 -0
- sphinx_autoapi-3.3.3.dist-info/WHEEL +4 -0
autoapi/__init__.py
ADDED
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import builtins
|
|
4
|
+
from collections.abc import Iterable
|
|
5
|
+
import itertools
|
|
6
|
+
import re
|
|
7
|
+
import sys
|
|
8
|
+
from typing import Any, NamedTuple
|
|
9
|
+
|
|
10
|
+
import astroid
|
|
11
|
+
import astroid.nodes
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
if sys.version_info < (3, 10): # PY310
|
|
15
|
+
from stdlib_list import in_stdlib
|
|
16
|
+
else:
|
|
17
|
+
|
|
18
|
+
def in_stdlib(module_name: str) -> bool:
|
|
19
|
+
return module_name in sys.stdlib_module_names
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ArgInfo(NamedTuple):
|
|
23
|
+
prefix: str | None
|
|
24
|
+
name: str | None
|
|
25
|
+
annotation: str | None
|
|
26
|
+
default_value: str | None
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def resolve_import_alias(
|
|
30
|
+
name: str, import_names: Iterable[tuple[str, str | None]]
|
|
31
|
+
) -> str:
|
|
32
|
+
"""Resolve a name from an aliased import to its original name.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
name: The potentially aliased name to resolve.
|
|
36
|
+
import_names: The pairs of original names and aliases from the import.
|
|
37
|
+
|
|
38
|
+
Returns:
|
|
39
|
+
The original name.
|
|
40
|
+
"""
|
|
41
|
+
resolved_name = name
|
|
42
|
+
|
|
43
|
+
for import_name, imported_as in import_names:
|
|
44
|
+
if import_name == name:
|
|
45
|
+
break
|
|
46
|
+
if imported_as == name:
|
|
47
|
+
resolved_name = import_name
|
|
48
|
+
break
|
|
49
|
+
|
|
50
|
+
return resolved_name
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_full_import_name(import_from: astroid.nodes.ImportFrom, name: str) -> str:
|
|
54
|
+
"""Get the full path of a name from a ``from x import y`` statement.
|
|
55
|
+
|
|
56
|
+
Args:
|
|
57
|
+
import_from: The astroid node to resolve the name of.
|
|
58
|
+
name: The short name or alias of what was imported.
|
|
59
|
+
This is ``y`` in ``from x import y``
|
|
60
|
+
and ``z`` in ``from x import y as z``.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
str: The full import path of the name.
|
|
64
|
+
"""
|
|
65
|
+
partial_basename = resolve_import_alias(name, import_from.names)
|
|
66
|
+
|
|
67
|
+
module_name = import_from.modname
|
|
68
|
+
if import_from.level:
|
|
69
|
+
module = import_from.root()
|
|
70
|
+
assert isinstance(module, astroid.nodes.Module)
|
|
71
|
+
module_name = module.relative_to_absolute_name(
|
|
72
|
+
import_from.modname, level=import_from.level
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return f"{module_name}.{partial_basename}"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def resolve_qualname(node: astroid.nodes.NodeNG, basename: str) -> str:
|
|
79
|
+
"""Resolve where a node is defined to get its fully qualified name.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
node: The node representing the base name.
|
|
83
|
+
basename: The partial base name to resolve.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
The fully resolved base name.
|
|
87
|
+
"""
|
|
88
|
+
full_basename = basename
|
|
89
|
+
|
|
90
|
+
top_level_name = re.sub(r"\(.*\)", "", basename).split(".", 1)[0]
|
|
91
|
+
if isinstance(node, astroid.nodes.LocalsDictNodeNG):
|
|
92
|
+
lookup_node = node
|
|
93
|
+
else:
|
|
94
|
+
lookup_node = node.scope()
|
|
95
|
+
|
|
96
|
+
assigns = lookup_node.lookup(top_level_name)[1]
|
|
97
|
+
|
|
98
|
+
for assignment in assigns:
|
|
99
|
+
if isinstance(assignment, astroid.nodes.ImportFrom):
|
|
100
|
+
import_name = get_full_import_name(assignment, top_level_name)
|
|
101
|
+
full_basename = basename.replace(top_level_name, import_name, 1)
|
|
102
|
+
break
|
|
103
|
+
if isinstance(assignment, astroid.nodes.Import):
|
|
104
|
+
import_name = resolve_import_alias(top_level_name, assignment.names)
|
|
105
|
+
full_basename = basename.replace(top_level_name, import_name, 1)
|
|
106
|
+
break
|
|
107
|
+
if isinstance(assignment, astroid.nodes.ClassDef):
|
|
108
|
+
full_basename = assignment.qname()
|
|
109
|
+
break
|
|
110
|
+
if isinstance(assignment, astroid.nodes.AssignName):
|
|
111
|
+
full_basename = f"{assignment.scope().qname()}.{assignment.name}"
|
|
112
|
+
|
|
113
|
+
if isinstance(node, astroid.nodes.Call):
|
|
114
|
+
full_basename = re.sub(r"\(.*\)", "()", full_basename)
|
|
115
|
+
|
|
116
|
+
if full_basename.startswith("builtins."):
|
|
117
|
+
return full_basename[len("builtins.") :]
|
|
118
|
+
|
|
119
|
+
if full_basename.startswith("__builtin__."):
|
|
120
|
+
return full_basename[len("__builtin__.") :]
|
|
121
|
+
|
|
122
|
+
return full_basename
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def get_full_basenames(node: astroid.nodes.ClassDef) -> Iterable[str]:
|
|
126
|
+
"""Resolve the partial names of a class' bases to fully qualified names.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
node: The class definition node to resolve the bases of.
|
|
130
|
+
|
|
131
|
+
Returns:
|
|
132
|
+
The fully qualified names.
|
|
133
|
+
"""
|
|
134
|
+
for base in node.bases:
|
|
135
|
+
yield _resolve_annotation(base)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def _get_const_value(node: astroid.nodes.NodeNG) -> str | None:
|
|
139
|
+
if isinstance(node, astroid.nodes.Const):
|
|
140
|
+
if isinstance(node.value, str) and "\n" in node.value:
|
|
141
|
+
return f'"""{node.value}"""'
|
|
142
|
+
|
|
143
|
+
class NotConstException(Exception):
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
def _inner(node: astroid.nodes.NodeNG) -> Any:
|
|
147
|
+
if isinstance(node, (astroid.nodes.List, astroid.nodes.Tuple)):
|
|
148
|
+
new_value = []
|
|
149
|
+
for element in node.elts:
|
|
150
|
+
new_value.append(_inner(element))
|
|
151
|
+
|
|
152
|
+
if isinstance(node, astroid.nodes.Tuple):
|
|
153
|
+
return tuple(new_value)
|
|
154
|
+
|
|
155
|
+
return new_value
|
|
156
|
+
elif isinstance(node, astroid.nodes.Const):
|
|
157
|
+
# Don't allow multi-line strings inside a data structure.
|
|
158
|
+
if isinstance(node.value, str) and "\n" in node.value:
|
|
159
|
+
raise NotConstException()
|
|
160
|
+
|
|
161
|
+
return node.value
|
|
162
|
+
|
|
163
|
+
raise NotConstException()
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
result = _inner(node)
|
|
167
|
+
except NotConstException:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
return repr(result)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def get_assign_value(
|
|
174
|
+
node: astroid.nodes.Assign | astroid.nodes.AnnAssign,
|
|
175
|
+
) -> tuple[str, str | None] | None:
|
|
176
|
+
"""Get the name and value of the assignment of the given node.
|
|
177
|
+
|
|
178
|
+
Assignments to multiple names are ignored, as per PEP 257.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
node: The node to get the assignment value from.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
The name that is assigned to, and the string representation of
|
|
185
|
+
the value assigned to the name (if it can be converted).
|
|
186
|
+
"""
|
|
187
|
+
try:
|
|
188
|
+
targets = node.targets
|
|
189
|
+
except AttributeError:
|
|
190
|
+
targets = [node.target]
|
|
191
|
+
|
|
192
|
+
if len(targets) == 1:
|
|
193
|
+
target = targets[0]
|
|
194
|
+
if isinstance(target, astroid.nodes.AssignName):
|
|
195
|
+
name = target.name
|
|
196
|
+
elif isinstance(target, astroid.nodes.AssignAttr):
|
|
197
|
+
name = target.attrname
|
|
198
|
+
else:
|
|
199
|
+
return None
|
|
200
|
+
return (name, _get_const_value(node.value))
|
|
201
|
+
|
|
202
|
+
return None
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def get_assign_annotation(
|
|
206
|
+
node: astroid.nodes.Assign | astroid.nodes.AnnAssign,
|
|
207
|
+
) -> str | None:
|
|
208
|
+
"""Get the type annotation of the assignment of the given node.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
node: The node to get the annotation for.
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
The type annotation as a string, or None if one does not exist.
|
|
215
|
+
"""
|
|
216
|
+
annotation_node = None
|
|
217
|
+
try:
|
|
218
|
+
annotation_node = node.annotation
|
|
219
|
+
except AttributeError:
|
|
220
|
+
annotation_node = node.type_annotation
|
|
221
|
+
|
|
222
|
+
return format_annotation(annotation_node)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def is_decorated_with_property(node: astroid.nodes.FunctionDef) -> bool:
|
|
226
|
+
"""Check if the function is decorated as a property.
|
|
227
|
+
|
|
228
|
+
Args:
|
|
229
|
+
node: The node to check.
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
True if the function is a property, False otherwise.
|
|
233
|
+
"""
|
|
234
|
+
if not node.decorators:
|
|
235
|
+
return False
|
|
236
|
+
|
|
237
|
+
for decorator in node.decorators.nodes:
|
|
238
|
+
if not isinstance(decorator, astroid.nodes.Name):
|
|
239
|
+
continue
|
|
240
|
+
|
|
241
|
+
try:
|
|
242
|
+
if _is_property_decorator(decorator):
|
|
243
|
+
return True
|
|
244
|
+
except astroid.InferenceError:
|
|
245
|
+
pass
|
|
246
|
+
|
|
247
|
+
return False
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def _is_property_decorator(decorator: astroid.nodes.Name) -> bool:
|
|
251
|
+
def _is_property_class(class_node: astroid.nodes.ClassDef) -> bool:
|
|
252
|
+
return (
|
|
253
|
+
class_node.name == "property"
|
|
254
|
+
and class_node.root().name == builtins.__name__
|
|
255
|
+
) or (
|
|
256
|
+
class_node.name == "cached_property"
|
|
257
|
+
and class_node.root().name == "functools"
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
for inferred in decorator.infer():
|
|
261
|
+
if not isinstance(inferred, astroid.nodes.ClassDef):
|
|
262
|
+
continue
|
|
263
|
+
|
|
264
|
+
if _is_property_class(inferred):
|
|
265
|
+
return True
|
|
266
|
+
|
|
267
|
+
if any(_is_property_class(ancestor) for ancestor in inferred.ancestors()):
|
|
268
|
+
return True
|
|
269
|
+
|
|
270
|
+
return False
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def is_decorated_with_property_setter(node: astroid.nodes.FunctionDef) -> bool:
|
|
274
|
+
"""Check if the function is decorated as a property setter.
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
node: The node to check.
|
|
278
|
+
|
|
279
|
+
Returns:
|
|
280
|
+
True if the function is a property setter, False otherwise.
|
|
281
|
+
"""
|
|
282
|
+
if not node.decorators:
|
|
283
|
+
return False
|
|
284
|
+
|
|
285
|
+
for decorator in node.decorators.nodes:
|
|
286
|
+
if (
|
|
287
|
+
isinstance(decorator, astroid.nodes.Attribute)
|
|
288
|
+
and decorator.attrname == "setter"
|
|
289
|
+
):
|
|
290
|
+
return True
|
|
291
|
+
|
|
292
|
+
return False
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
def is_decorated_with_overload(node: astroid.nodes.FunctionDef) -> bool:
|
|
296
|
+
"""Check if the function is decorated as an overload definition.
|
|
297
|
+
|
|
298
|
+
Args:
|
|
299
|
+
node: The node to check.
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
True if the function is an overload definition, False otherwise.
|
|
303
|
+
"""
|
|
304
|
+
if not node.decorators:
|
|
305
|
+
return False
|
|
306
|
+
|
|
307
|
+
for decorator in node.decorators.nodes:
|
|
308
|
+
if not isinstance(decorator, (astroid.nodes.Name, astroid.nodes.Attribute)):
|
|
309
|
+
continue
|
|
310
|
+
|
|
311
|
+
try:
|
|
312
|
+
if _is_overload_decorator(decorator):
|
|
313
|
+
return True
|
|
314
|
+
except astroid.InferenceError:
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
return False
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def _is_overload_decorator(
|
|
321
|
+
decorator: astroid.nodes.Name | astroid.nodes.Attribute,
|
|
322
|
+
) -> bool:
|
|
323
|
+
for inferred in decorator.infer():
|
|
324
|
+
if not isinstance(inferred, astroid.nodes.FunctionDef):
|
|
325
|
+
continue
|
|
326
|
+
|
|
327
|
+
if inferred.name == "overload" and inferred.root().name == "typing":
|
|
328
|
+
return True
|
|
329
|
+
|
|
330
|
+
return False
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def is_constructor(node: astroid.nodes.FunctionDef) -> bool:
|
|
334
|
+
"""Check if the function is a constructor.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
node: The node to check.
|
|
338
|
+
|
|
339
|
+
Returns:
|
|
340
|
+
True if the function is a constructor, False otherwise.
|
|
341
|
+
"""
|
|
342
|
+
return (
|
|
343
|
+
node.parent
|
|
344
|
+
and isinstance(node.parent.scope(), astroid.nodes.ClassDef)
|
|
345
|
+
and node.name == "__init__"
|
|
346
|
+
)
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
def is_exception(node: astroid.nodes.ClassDef) -> bool:
|
|
350
|
+
"""Check if a class is an exception.
|
|
351
|
+
|
|
352
|
+
Args:
|
|
353
|
+
node: The node to check.
|
|
354
|
+
|
|
355
|
+
Returns:
|
|
356
|
+
True if the class is an exception, False otherwise.
|
|
357
|
+
"""
|
|
358
|
+
if node.name in ("Exception", "BaseException") and node.root().name == "builtins":
|
|
359
|
+
return True
|
|
360
|
+
|
|
361
|
+
if not hasattr(node, "ancestors"):
|
|
362
|
+
return False
|
|
363
|
+
|
|
364
|
+
return any(is_exception(parent) for parent in node.ancestors(recurs=True))
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
def is_local_import_from(node: astroid.nodes.NodeNG, package_name: str) -> bool:
|
|
368
|
+
"""Check if a node is an import from the local package.
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
node: The node to check.
|
|
372
|
+
package_name: The name of the local package.
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
True if the node is an import from the local package. False otherwise.
|
|
376
|
+
"""
|
|
377
|
+
if not isinstance(node, astroid.nodes.ImportFrom):
|
|
378
|
+
return False
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
node.level
|
|
382
|
+
or node.modname == package_name
|
|
383
|
+
or node.modname.startswith(package_name + ".")
|
|
384
|
+
)
|
|
385
|
+
|
|
386
|
+
|
|
387
|
+
def get_module_all(node: astroid.nodes.Module) -> list[str] | None:
|
|
388
|
+
"""Get the contents of the ``__all__`` variable from a module.
|
|
389
|
+
|
|
390
|
+
Args:
|
|
391
|
+
node: The module to get ``__all__`` from.
|
|
392
|
+
|
|
393
|
+
Returns:
|
|
394
|
+
The contents of ``__all__`` if defined. Otherwise ``None``.
|
|
395
|
+
"""
|
|
396
|
+
all_ = None
|
|
397
|
+
|
|
398
|
+
if "__all__" in node.locals:
|
|
399
|
+
assigned = next(node.igetattr("__all__"))
|
|
400
|
+
if assigned is not astroid.Uninferable:
|
|
401
|
+
all_ = []
|
|
402
|
+
for elt in getattr(assigned, "elts", ()):
|
|
403
|
+
try:
|
|
404
|
+
elt_name = next(elt.infer())
|
|
405
|
+
except astroid.InferenceError:
|
|
406
|
+
continue
|
|
407
|
+
|
|
408
|
+
if elt_name is astroid.Uninferable:
|
|
409
|
+
continue
|
|
410
|
+
|
|
411
|
+
if isinstance(elt_name, astroid.nodes.Const) and isinstance(
|
|
412
|
+
elt_name.value, str
|
|
413
|
+
):
|
|
414
|
+
all_.append(elt_name.value)
|
|
415
|
+
|
|
416
|
+
return all_
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
def _is_ellipsis(node: astroid.nodes.NodeNG) -> bool:
|
|
420
|
+
return isinstance(node, astroid.nodes.Const) and node.value == Ellipsis
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def merge_annotations(
|
|
424
|
+
annotations: Iterable[astroid.nodes.NodeNG],
|
|
425
|
+
comment_annotations: Iterable[astroid.nodes.NodeNG],
|
|
426
|
+
) -> Iterable[astroid.nodes.NodeNG | None]:
|
|
427
|
+
for ann, comment_ann in itertools.zip_longest(annotations, comment_annotations):
|
|
428
|
+
if ann and not _is_ellipsis(ann):
|
|
429
|
+
yield ann
|
|
430
|
+
elif comment_ann and not _is_ellipsis(comment_ann):
|
|
431
|
+
yield comment_ann
|
|
432
|
+
else:
|
|
433
|
+
yield None
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
def _resolve_annotation(annotation: astroid.nodes.NodeNG) -> str:
|
|
437
|
+
resolved: str
|
|
438
|
+
|
|
439
|
+
if isinstance(annotation, astroid.nodes.Const):
|
|
440
|
+
resolved = resolve_qualname(annotation, str(annotation.value))
|
|
441
|
+
elif isinstance(annotation, astroid.nodes.Name):
|
|
442
|
+
resolved = resolve_qualname(annotation, annotation.name)
|
|
443
|
+
elif isinstance(annotation, astroid.nodes.Attribute):
|
|
444
|
+
resolved = resolve_qualname(annotation, annotation.as_string())
|
|
445
|
+
elif isinstance(annotation, astroid.nodes.Subscript):
|
|
446
|
+
value = _resolve_annotation(annotation.value)
|
|
447
|
+
slice_node = annotation.slice
|
|
448
|
+
# astroid.Index was removed in astroid v3
|
|
449
|
+
if hasattr(astroid.nodes, "Index") and isinstance(
|
|
450
|
+
slice_node, astroid.nodes.Index
|
|
451
|
+
):
|
|
452
|
+
slice_node = slice_node.value
|
|
453
|
+
if value == "Literal":
|
|
454
|
+
if isinstance(slice_node, astroid.nodes.Tuple):
|
|
455
|
+
elts = slice_node.elts
|
|
456
|
+
else:
|
|
457
|
+
elts = [slice_node]
|
|
458
|
+
slice_ = ", ".join(
|
|
459
|
+
(
|
|
460
|
+
elt.as_string()
|
|
461
|
+
if isinstance(elt, astroid.nodes.Const)
|
|
462
|
+
else _resolve_annotation(elt)
|
|
463
|
+
)
|
|
464
|
+
for elt in elts
|
|
465
|
+
)
|
|
466
|
+
elif isinstance(slice_node, astroid.nodes.Tuple):
|
|
467
|
+
slice_ = ", ".join(_resolve_annotation(elt) for elt in slice_node.elts)
|
|
468
|
+
else:
|
|
469
|
+
slice_ = _resolve_annotation(slice_node)
|
|
470
|
+
resolved = f"{value}[{slice_}]"
|
|
471
|
+
elif isinstance(annotation, astroid.nodes.Tuple):
|
|
472
|
+
resolved = (
|
|
473
|
+
"(" + ", ".join(_resolve_annotation(elt) for elt in annotation.elts) + ")"
|
|
474
|
+
)
|
|
475
|
+
elif isinstance(annotation, astroid.nodes.List):
|
|
476
|
+
resolved = (
|
|
477
|
+
"[" + ", ".join(_resolve_annotation(elt) for elt in annotation.elts) + "]"
|
|
478
|
+
)
|
|
479
|
+
elif isinstance(annotation, astroid.nodes.BinOp) and annotation.op == "|":
|
|
480
|
+
left = _resolve_annotation(annotation.left)
|
|
481
|
+
right = _resolve_annotation(annotation.right)
|
|
482
|
+
resolved = f"{left} | {right}"
|
|
483
|
+
else:
|
|
484
|
+
resolved = annotation.as_string()
|
|
485
|
+
|
|
486
|
+
if resolved.startswith("typing."):
|
|
487
|
+
return resolved[len("typing.") :]
|
|
488
|
+
|
|
489
|
+
# Sphinx is capable of linking anything in the same module
|
|
490
|
+
# without needing a fully qualified path.
|
|
491
|
+
module_prefix = annotation.root().name + "."
|
|
492
|
+
if resolved.startswith(module_prefix):
|
|
493
|
+
return resolved[len(module_prefix) :]
|
|
494
|
+
|
|
495
|
+
return resolved
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def format_annotation(annotation: astroid.nodes.NodeNG | None) -> str | None:
|
|
499
|
+
if annotation:
|
|
500
|
+
return _resolve_annotation(annotation)
|
|
501
|
+
|
|
502
|
+
return annotation
|
|
503
|
+
|
|
504
|
+
|
|
505
|
+
def _iter_args(
|
|
506
|
+
args: list[astroid.nodes.AssignName],
|
|
507
|
+
annotations: list[astroid.nodes.NodeNG | None],
|
|
508
|
+
defaults: list[astroid.nodes.NodeNG],
|
|
509
|
+
) -> Iterable[tuple[str, str | None, str | None]]:
|
|
510
|
+
default_offset = len(args) - len(defaults)
|
|
511
|
+
packed = itertools.zip_longest(args, annotations)
|
|
512
|
+
for i, (arg, annotation) in enumerate(packed):
|
|
513
|
+
default = None
|
|
514
|
+
if defaults is not None and i >= default_offset:
|
|
515
|
+
if defaults[i - default_offset] is not None:
|
|
516
|
+
default = defaults[i - default_offset].as_string()
|
|
517
|
+
|
|
518
|
+
name = arg.name
|
|
519
|
+
if isinstance(arg, astroid.nodes.Tuple):
|
|
520
|
+
argument_names = ", ".join(x.name for x in arg.elts)
|
|
521
|
+
name = f"({argument_names})"
|
|
522
|
+
|
|
523
|
+
yield (name, format_annotation(annotation), default)
|
|
524
|
+
|
|
525
|
+
|
|
526
|
+
def get_args_info(args_node: astroid.nodes.Arguments) -> list[ArgInfo]:
|
|
527
|
+
result: list[ArgInfo] = []
|
|
528
|
+
positional_only_defaults = []
|
|
529
|
+
positional_or_keyword_defaults = args_node.defaults
|
|
530
|
+
if args_node.defaults:
|
|
531
|
+
args = args_node.args or []
|
|
532
|
+
positional_or_keyword_defaults = args_node.defaults[-len(args) :]
|
|
533
|
+
positional_only_defaults = args_node.defaults[
|
|
534
|
+
: len(args_node.defaults) - len(args)
|
|
535
|
+
]
|
|
536
|
+
|
|
537
|
+
plain_annotations = args_node.annotations or ()
|
|
538
|
+
|
|
539
|
+
func_comment_annotations = args_node.parent.type_comment_args or []
|
|
540
|
+
if args_node.parent.type in ("method", "classmethod"):
|
|
541
|
+
func_comment_annotations = [None] + func_comment_annotations
|
|
542
|
+
|
|
543
|
+
comment_annotations = args_node.type_comment_posonlyargs
|
|
544
|
+
comment_annotations += args_node.type_comment_args or []
|
|
545
|
+
comment_annotations += args_node.type_comment_kwonlyargs
|
|
546
|
+
annotations = list(
|
|
547
|
+
merge_annotations(
|
|
548
|
+
plain_annotations,
|
|
549
|
+
merge_annotations(func_comment_annotations, comment_annotations),
|
|
550
|
+
)
|
|
551
|
+
)
|
|
552
|
+
annotation_offset = 0
|
|
553
|
+
|
|
554
|
+
if args_node.posonlyargs:
|
|
555
|
+
posonlyargs_annotations = args_node.posonlyargs_annotations
|
|
556
|
+
if not any(args_node.posonlyargs_annotations):
|
|
557
|
+
num_args = len(args_node.posonlyargs)
|
|
558
|
+
posonlyargs_annotations = annotations[
|
|
559
|
+
annotation_offset : annotation_offset + num_args
|
|
560
|
+
]
|
|
561
|
+
|
|
562
|
+
for arg, annotation, default in _iter_args(
|
|
563
|
+
args_node.posonlyargs, posonlyargs_annotations, positional_only_defaults
|
|
564
|
+
):
|
|
565
|
+
result.append(ArgInfo(None, arg, annotation, default))
|
|
566
|
+
|
|
567
|
+
result.append(ArgInfo("/", None, None, None))
|
|
568
|
+
|
|
569
|
+
if not any(args_node.posonlyargs_annotations):
|
|
570
|
+
annotation_offset += num_args
|
|
571
|
+
|
|
572
|
+
if args_node.args:
|
|
573
|
+
num_args = len(args_node.args)
|
|
574
|
+
for arg, annotation, default in _iter_args(
|
|
575
|
+
args_node.args,
|
|
576
|
+
annotations[annotation_offset : annotation_offset + num_args],
|
|
577
|
+
positional_or_keyword_defaults,
|
|
578
|
+
):
|
|
579
|
+
result.append(ArgInfo(None, arg, annotation, default))
|
|
580
|
+
|
|
581
|
+
annotation_offset += num_args
|
|
582
|
+
|
|
583
|
+
if args_node.vararg:
|
|
584
|
+
annotation = None
|
|
585
|
+
if args_node.varargannotation:
|
|
586
|
+
annotation = format_annotation(args_node.varargannotation)
|
|
587
|
+
elif len(annotations) > annotation_offset and annotations[annotation_offset]:
|
|
588
|
+
annotation = format_annotation(annotations[annotation_offset])
|
|
589
|
+
annotation_offset += 1
|
|
590
|
+
result.append(ArgInfo("*", args_node.vararg, annotation, None))
|
|
591
|
+
|
|
592
|
+
if args_node.kwonlyargs:
|
|
593
|
+
if not args_node.vararg:
|
|
594
|
+
result.append(ArgInfo("*", None, None, None))
|
|
595
|
+
|
|
596
|
+
kwonlyargs_annotations = args_node.kwonlyargs_annotations
|
|
597
|
+
if not any(args_node.kwonlyargs_annotations):
|
|
598
|
+
num_args = len(args_node.kwonlyargs)
|
|
599
|
+
kwonlyargs_annotations = annotations[
|
|
600
|
+
annotation_offset : annotation_offset + num_args
|
|
601
|
+
]
|
|
602
|
+
|
|
603
|
+
for arg, annotation, default in _iter_args(
|
|
604
|
+
args_node.kwonlyargs,
|
|
605
|
+
kwonlyargs_annotations,
|
|
606
|
+
args_node.kw_defaults,
|
|
607
|
+
):
|
|
608
|
+
result.append(ArgInfo(None, arg, annotation, default))
|
|
609
|
+
|
|
610
|
+
if not any(args_node.kwonlyargs_annotations):
|
|
611
|
+
annotation_offset += num_args
|
|
612
|
+
|
|
613
|
+
if args_node.kwarg:
|
|
614
|
+
annotation = None
|
|
615
|
+
if args_node.kwargannotation:
|
|
616
|
+
annotation = format_annotation(args_node.kwargannotation)
|
|
617
|
+
elif len(annotations) > annotation_offset and annotations[annotation_offset]:
|
|
618
|
+
annotation = format_annotation(annotations[annotation_offset])
|
|
619
|
+
annotation_offset += 1
|
|
620
|
+
result.append(ArgInfo("**", args_node.kwarg, annotation, None))
|
|
621
|
+
|
|
622
|
+
if args_node.parent.type in ("method", "classmethod") and result:
|
|
623
|
+
result.pop(0)
|
|
624
|
+
|
|
625
|
+
return result
|
|
626
|
+
|
|
627
|
+
|
|
628
|
+
def get_return_annotation(node: astroid.nodes.FunctionDef) -> str | None:
|
|
629
|
+
"""Get the return annotation of a node.
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
node: The node to get the return annotation for.
|
|
633
|
+
|
|
634
|
+
Returns:
|
|
635
|
+
The return annotation of the given node.
|
|
636
|
+
"""
|
|
637
|
+
return_annotation = None
|
|
638
|
+
|
|
639
|
+
if node.returns:
|
|
640
|
+
return_annotation = format_annotation(node.returns)
|
|
641
|
+
elif node.type_comment_returns:
|
|
642
|
+
return_annotation = format_annotation(node.type_comment_returns)
|
|
643
|
+
|
|
644
|
+
return return_annotation
|
|
645
|
+
|
|
646
|
+
|
|
647
|
+
def get_class_docstring(node: astroid.nodes.ClassDef) -> str:
|
|
648
|
+
"""Get the docstring of a node, using a parent docstring if needed.
|
|
649
|
+
|
|
650
|
+
Args:
|
|
651
|
+
node: The node to get a docstring for.
|
|
652
|
+
|
|
653
|
+
Returns:
|
|
654
|
+
The docstring of the class, or the empty string if no docstring
|
|
655
|
+
was found or defined.
|
|
656
|
+
"""
|
|
657
|
+
doc = node.doc_node.value if node.doc_node else ""
|
|
658
|
+
|
|
659
|
+
if not doc:
|
|
660
|
+
for base in node.ancestors():
|
|
661
|
+
if base.qname() in (
|
|
662
|
+
"__builtins__.object",
|
|
663
|
+
"builtins.object",
|
|
664
|
+
"builtins.type",
|
|
665
|
+
):
|
|
666
|
+
continue
|
|
667
|
+
if base.doc_node is not None:
|
|
668
|
+
return base.doc_node.value
|
|
669
|
+
|
|
670
|
+
return doc
|
|
671
|
+
|
|
672
|
+
|
|
673
|
+
def is_abstract_class(node: astroid.nodes.ClassDef) -> bool:
|
|
674
|
+
metaclass = node.metaclass()
|
|
675
|
+
if metaclass and metaclass.name == "ABCMeta":
|
|
676
|
+
return True
|
|
677
|
+
|
|
678
|
+
if "abc.ABC" in node.basenames:
|
|
679
|
+
return True
|
|
680
|
+
|
|
681
|
+
if any(method.is_abstract(pass_is_abstract=False) for method in node.methods()):
|
|
682
|
+
return True
|
|
683
|
+
|
|
684
|
+
return False
|