Skip to content

Commit 5c0f650

Browse files
committed
Various fixes for own page output
Also added tests for own page output. Fix some inherited members always being rendered. Own page members of an entity are linked to after the docstring of the parent entity. Fix entities below the "class" level that have their own page from rendering incorrectly. Rename "single page output" to "own page output". An entity does not have a "single page" when its members are spread across their own pages. Properties are linked to on their parent classes page. Children not present in `__all__` are not rendered. Fixed emitting ignore event twice for methods. Corrected documentation around `imported-members` to reflect that it applies only to objects imported into a package, not modules. Fixed path error on Windows.
1 parent d5807f4 commit 5c0f650

File tree

28 files changed

+1460
-762
lines changed

28 files changed

+1460
-762
lines changed

autoapi/directives.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class NestedParse(Directive):
4848
duplicate headings on sections.
4949
"""
5050

51-
has_content = 1
51+
has_content = True
5252
required_arguments = 0
5353
optional_arguments = 0
5454
final_argument_whitespace = False

autoapi/extension.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@
3535
"special-members",
3636
"imported-members",
3737
]
38+
_VALID_PAGE_LEVELS = [
39+
"module",
40+
"class",
41+
"function",
42+
"method",
43+
"attribute",
44+
]
3845
_VIEWCODE_CACHE: Dict[str, Tuple[str, Dict]] = {}
3946
"""Caches a module's parse results for use in viewcode."""
4047

@@ -75,6 +82,10 @@ def run_autoapi(app):
7582
if app.config.autoapi_include_summaries:
7683
app.config.autoapi_options.append("show-module-summary")
7784

85+
own_page_level = app.config.autoapi_own_page_level
86+
if own_page_level not in _VALID_PAGE_LEVELS:
87+
raise ValueError(f"Invalid autoapi_own_page_level '{own_page_level}")
88+
7889
# Make sure the paths are full
7990
normalised_dirs = _normalise_autoapi_dirs(app.config.autoapi_dirs, app.srcdir)
8091
for _dir in normalised_dirs:
@@ -101,7 +112,7 @@ def run_autoapi(app):
101112
RemovedInAutoAPI3Warning,
102113
)
103114
sphinx_mapper_obj = PythonSphinxMapper(
104-
app, template_dir=template_dir, url_root=url_root
115+
app, template_dir=template_dir, dir_root=normalized_root, url_root=url_root
105116
)
106117

107118
if app.config.autoapi_file_patterns:
@@ -128,7 +139,7 @@ def run_autoapi(app):
128139
sphinx_mapper_obj.map(options=app.config.autoapi_options)
129140

130141
if app.config.autoapi_generate_api_docs:
131-
sphinx_mapper_obj.output_rst(root=normalized_root, source_suffix=out_suffix)
142+
sphinx_mapper_obj.output_rst(source_suffix=out_suffix)
132143

133144

134145
def build_finished(app, exception):

autoapi/mappers/base.py

Lines changed: 37 additions & 102 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import os
2-
import fnmatch
31
from collections import OrderedDict, namedtuple
2+
import fnmatch
3+
import os
4+
import pathlib
45
import re
56

6-
import anyascii
7-
from docutils.parsers.rst import convert_directive_function
87
from jinja2 import Environment, FileSystemLoader, TemplateNotFound
98
import sphinx
109
import sphinx.util
@@ -13,7 +12,7 @@
1312
from sphinx.util.osutil import ensuredir
1413
import sphinx.util.logging
1514

16-
from ..settings import API_ROOT, TEMPLATE_DIR
15+
from ..settings import TEMPLATE_DIR
1716

1817
LOGGER = sphinx.util.logging.getLogger(__name__)
1918
_OWN_PAGE_LEVELS = [
@@ -24,8 +23,8 @@
2423
"function",
2524
"method",
2625
"property",
27-
"attribute",
2826
"data",
27+
"attribute",
2928
]
3029

3130
Path = namedtuple("Path", ["absolute", "relative"])
@@ -38,14 +37,10 @@ class PythonMapperBase:
3837
and map that onto this standard Python object.
3938
Subclasses may also include language-specific attributes on this object.
4039
41-
Arguments:
42-
4340
Args:
4441
obj: JSON object representing this object
4542
jinja_env: A template environment for rendering this object
4643
47-
Required attributes:
48-
4944
Attributes:
5045
id (str): A globally unique identifier for this object.
5146
Generally a fully qualified name, including namespace.
@@ -55,25 +50,21 @@ class PythonMapperBase:
5550
children (list): Children of this object
5651
parameters (list): Parameters to this object
5752
methods (list): Methods on this object
58-
59-
Optional attributes:
60-
6153
"""
6254

6355
language = "base"
6456
type = "base"
65-
# Create a page in the output for this object.
66-
top_level_object = False
6757
_RENDER_LOG_LEVEL = "VERBOSE"
6858

69-
def __init__(self, obj, jinja_env, app, options=None):
59+
def __init__(self, obj, jinja_env, app, url_root, options=None):
7060
self.app = app
7161
self.obj = obj
7262
self.options = options
7363
self.jinja_env = jinja_env
74-
self.url_root = os.path.join("/", API_ROOT)
64+
self.url_root = url_root
7565

7666
self.name = None
67+
self.qual_name = None
7768
self.id = None
7869

7970
def __getstate__(self):
@@ -103,7 +94,7 @@ def rendered(self):
10394
def get_context_data(self):
10495
own_page_level = self.app.config.autoapi_own_page_level
10596
desired_page_level = _OWN_PAGE_LEVELS.index(own_page_level)
106-
own_page_types = set(_OWN_PAGE_LEVELS[:desired_page_level+1])
97+
own_page_types = set(_OWN_PAGE_LEVELS[: desired_page_level + 1])
10798

10899
return {
109100
"autoapi_options": self.app.config.autoapi_options,
@@ -127,28 +118,19 @@ def short_name(self):
127118
"""Shorten name property"""
128119
return self.name.split(".")[-1]
129120

130-
@property
131-
def pathname(self):
132-
"""Sluggified path for filenames
121+
def output_dir(self, root):
122+
"""The directory to render this object."""
123+
module = self.id[: -(len("." + self.qual_name))]
124+
parts = [root] + module.split(".")
125+
return pathlib.PurePosixPath(*parts)
133126

134-
Slugs to a filename using the follow steps
127+
def output_filename(self):
128+
"""The name of the file to render into, without a file suffix."""
129+
filename = self.qual_name
130+
if filename == "index":
131+
filename = ".index"
135132

136-
* Decode unicode to approximate ascii
137-
* Remove existing hyphens
138-
* Substitute hyphens for non-word characters
139-
* Break up the string as paths
140-
"""
141-
slug = self.name
142-
slug = anyascii.anyascii(slug)
143-
slug = slug.replace("-", "")
144-
slug = re.sub(r"[^\w\.]+", "-", slug).strip("-")
145-
return os.path.join(*slug.split("."))
146-
147-
def include_dir(self, root):
148-
"""Return directory of file"""
149-
parts = [root]
150-
parts.extend(self.pathname.split(os.path.sep))
151-
return "/".join(parts)
133+
return filename
152134

153135
@property
154136
def include_path(self):
@@ -157,9 +139,7 @@ def include_path(self):
157139
This is used in ``toctree`` directives, as Sphinx always expects Unix
158140
path separators
159141
"""
160-
parts = [self.include_dir(root=self.url_root)]
161-
parts.append("index")
162-
return "/".join(parts)
142+
return str(self.output_dir(self.url_root) / self.output_filename())
163143

164144
@property
165145
def display(self):
@@ -185,7 +165,7 @@ class SphinxMapperBase:
185165
app: Sphinx application instance
186166
"""
187167

188-
def __init__(self, app, template_dir=None, url_root=None):
168+
def __init__(self, app, template_dir=None, dir_root=None, url_root=None):
189169
self.app = app
190170

191171
template_paths = [TEMPLATE_DIR]
@@ -209,8 +189,9 @@ def _wrapped_prepare(value):
209189

210190
own_page_level = self.app.config.autoapi_own_page_level
211191
desired_page_level = _OWN_PAGE_LEVELS.index(own_page_level)
212-
self.own_page_types = set(_OWN_PAGE_LEVELS[:desired_page_level+1])
192+
self.own_page_types = set(_OWN_PAGE_LEVELS[: desired_page_level + 1])
213193

194+
self.dir_root = dir_root
214195
self.url_root = url_root
215196

216197
# Mapping of {filepath -> raw data}
@@ -298,14 +279,17 @@ def add_object(self, obj):
298279
Args:
299280
obj: Instance of a AutoAPI object
300281
"""
301-
if obj.type in self.own_page_types:
282+
display = obj.display
283+
if display and obj.type in self.own_page_types:
302284
self.objects_to_render[obj.id] = obj
303285

304286
self.all_objects[obj.id] = obj
305287
child_stack = list(obj.children)
306288
while child_stack:
307289
child = child_stack.pop()
308290
self.all_objects[child.id] = child
291+
if display and child.type in self.own_page_types:
292+
self.objects_to_render[child.id] = child
309293
child_stack.extend(getattr(child, "children", ()))
310294

311295
def map(self, options=None):
@@ -327,81 +311,32 @@ def create_class(self, data, options=None, **kwargs):
327311
"""
328312
raise NotImplementedError
329313

330-
def output_child_rst(self, obj, obj_parent, detail_dir, source_suffix):
331-
332-
if not obj.display:
333-
return
334-
335-
# Skip nested cases like functions in functions or clases in clases
336-
if obj.type == obj_parent.type:
337-
return
338-
339-
obj_child_page_level = _OWN_PAGE_LEVELS.index(obj.type)
340-
desired_page_level = _OWN_PAGE_LEVELS.index(self.app.config.autoapi_own_page_level)
341-
is_own_page = obj_child_page_level <= desired_page_level
342-
if not is_own_page:
343-
return
344-
345-
obj_child_rst = obj.render(
346-
is_own_page=is_own_page,
347-
)
348-
if not obj_child_rst:
349-
return
350-
351-
function_page_level = _OWN_PAGE_LEVELS.index("function")
352-
is_level_beyond_function = function_page_level < desired_page_level
353-
if obj.type in ["exception", "class"]:
354-
if not is_level_beyond_function:
355-
outfile = f"{obj.short_name}{source_suffix}"
356-
path = os.path.join(detail_dir, outfile)
357-
else:
358-
outdir = os.path.join(detail_dir, obj.short_name)
359-
ensuredir(outdir)
360-
path = os.path.join(outdir, f"index{source_suffix}")
361-
else:
362-
is_parent_in_detail_dir = detail_dir.endswith(obj_parent.short_name)
363-
outdir = detail_dir if is_parent_in_detail_dir else os.path.join(detail_dir, obj_parent.short_name)
364-
ensuredir(outdir)
365-
path = os.path.join(outdir, f"{obj.short_name}{source_suffix}")
366-
367-
with open(path, "wb+") as obj_child_detail_file:
368-
obj_child_detail_file.write(obj_child_rst.encode("utf-8"))
369-
370-
for obj_child in obj.children:
371-
child_detail_dir = os.path.join(detail_dir, obj.name)
372-
self.output_child_rst(obj_child, obj, child_detail_dir, source_suffix)
373-
374-
def output_rst(self, root, source_suffix):
314+
def output_rst(self, source_suffix):
375315
for _, obj in status_iterator(
376316
self.objects_to_render.items(),
377317
colorize("bold", "[AutoAPI] ") + "Rendering Data... ",
378318
length=len(self.objects_to_render),
379319
verbosity=1,
380320
stringify_func=(lambda x: x[0]),
381321
):
382-
if not obj.display:
383-
continue
384-
385322
rst = obj.render(is_own_page=True)
386323
if not rst:
387324
continue
388325

389-
detail_dir = obj.include_dir(root=root)
390-
ensuredir(detail_dir)
391-
path = os.path.join(detail_dir, f"index{source_suffix}")
326+
output_dir = obj.output_dir(self.dir_root)
327+
ensuredir(output_dir)
328+
output_path = output_dir / obj.output_filename()
329+
path = f"{output_path}{source_suffix}"
392330
with open(path, "wb+") as detail_file:
393331
detail_file.write(rst.encode("utf-8"))
394-
395-
for child in obj.children:
396-
self.output_child_rst(child, obj, detail_dir, source_suffix)
397332

398333
if self.app.config.autoapi_add_toctree_entry:
399-
self._output_top_rst(root)
334+
self._output_top_rst()
400335

401-
def _output_top_rst(self, root):
336+
def _output_top_rst(self):
402337
# Render Top Index
403-
top_level_index = os.path.join(root, "index.rst")
404-
pages = self.objects_to_render.values()
338+
top_level_index = os.path.join(self.dir_root, "index.rst")
339+
pages = [obj for obj in self.objects_to_render.values() if obj.display]
405340
with open(top_level_index, "wb") as top_level_file:
406341
content = self.jinja_env.get_template("index.rst")
407342
top_level_file.write(content.render(pages=pages).encode("utf-8"))

0 commit comments

Comments
 (0)