1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
|
from dataclasses import dataclass
from functools import cached_property
from sphinxcontrib.serializinghtml import JSONHTMLBuilder
IGNORED_DOMAIN_TYPES = {"module"}
@dataclass
class DomainObject:
"""
A wrapper around sphinx's domain object descriptions
https://www.sphinx-doc.org/en/master/extdev/domainapi.html#sphinx.domains.Domain.get_objects
"""
name: str
dispname: str
type: str
docname: str
anchor: str
priority: int
@property
def short_name(self) -> str:
"""
Returns a shortened version of the object's name.
- If the second-to-last part of the name starts with an uppercase letter
(indicating a class method or class attribute), returns the last two parts.
- Otherwise, returns only the last part (likely a function, or class).
Examples:
- "django.db.models.query.QuerySet.select_related" → "QuerySet.select_related"
- "django.db.models.query.QuerySet" → "QuerySet"
- "django.template.context_processors.static" → "static"
"""
parts = self.name.split(".")
if len(parts) < 2:
return self.name
last, second_last = parts[-1], parts[-2]
return f"{second_last}.{last}" if second_last[0].isupper() else last
class PythonObjectsJSONHTMLBuilder(JSONHTMLBuilder):
@cached_property
def domain_objects(self):
domain = self.env.get_domain("py")
return [
DomainObject(*item)
for item in domain.get_objects()
if item[2] not in IGNORED_DOMAIN_TYPES # item[2] is 'type'.
]
def get_doc_context(self, docname, body, metatags):
out_dict = super().get_doc_context(docname, body, metatags)
python_objects = self.get_python_objects(docname)
out_dict["python_objects"] = python_objects
out_dict["python_objects_search"] = " ".join(
# Keeps the code suffix to improve the search results for terms such as
# "select" for QuerySet.select_related.
[key.split(".")[-1] for key in python_objects.keys()]
)
return out_dict
def get_python_objects(self, docname):
return {
obj.short_name: obj.name
for obj in self.domain_objects
if obj.docname == docname
}
def setup(app):
app.add_builder(PythonObjectsJSONHTMLBuilder, override=True)
# JSONHTMLBuilder marks parallel read/write as safe, so our implementation
# should also handle that.
return {
"version": "0.1",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
|