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.
pull/394/merge
Ashley Whetter 4 months ago
parent 2a603b8ac0
commit a6558dcfc2

@ -35,6 +35,13 @@ _DEFAULT_OPTIONS = [
"special-members",
"imported-members",
]
_VALID_PAGE_LEVELS = [
"module",
"class",
"function",
"method",
"attribute",
]
_VIEWCODE_CACHE: Dict[str, Tuple[str, Dict]] = {}
"""Caches a module's parse results for use in viewcode."""
@ -75,6 +82,10 @@ def run_autoapi(app):
if app.config.autoapi_include_summaries:
app.config.autoapi_options.append("show-module-summary")
own_page_level = app.config.autoapi_own_page_level
if own_page_level not in _VALID_PAGE_LEVELS:
raise ValueError(f"Invalid autoapi_own_page_level '{own_page_level}")
# Make sure the paths are full
normalised_dirs = _normalise_autoapi_dirs(app.config.autoapi_dirs, app.srcdir)
for _dir in normalised_dirs:
@ -101,7 +112,7 @@ def run_autoapi(app):
RemovedInAutoAPI3Warning,
)
sphinx_mapper_obj = PythonSphinxMapper(
app, template_dir=template_dir, url_root=url_root
app, template_dir=template_dir, dir_root=normalized_root, url_root=url_root
)
if app.config.autoapi_file_patterns:
@ -128,7 +139,7 @@ def run_autoapi(app):
sphinx_mapper_obj.map(options=app.config.autoapi_options)
if app.config.autoapi_generate_api_docs:
sphinx_mapper_obj.output_rst(root=normalized_root, source_suffix=out_suffix)
sphinx_mapper_obj.output_rst(source_suffix=out_suffix)
def build_finished(app, exception):

@ -1,10 +1,9 @@
import os
import fnmatch
from collections import OrderedDict, namedtuple
import fnmatch
import os
import pathlib
import re
import anyascii
from docutils.parsers.rst import convert_directive_function
from jinja2 import Environment, FileSystemLoader, TemplateNotFound
import sphinx
import sphinx.util
@ -13,7 +12,7 @@ from sphinx.util.display import status_iterator
from sphinx.util.osutil import ensuredir
import sphinx.util.logging
from ..settings import API_ROOT, TEMPLATE_DIR
from ..settings import TEMPLATE_DIR
LOGGER = sphinx.util.logging.getLogger(__name__)
_OWN_PAGE_LEVELS = [
@ -24,8 +23,8 @@ _OWN_PAGE_LEVELS = [
"function",
"method",
"property",
"attribute",
"data",
"attribute",
]
Path = namedtuple("Path", ["absolute", "relative"])
@ -38,14 +37,10 @@ class PythonMapperBase:
and map that onto this standard Python object.
Subclasses may also include language-specific attributes on this object.
Arguments:
Args:
obj: JSON object representing this object
jinja_env: A template environment for rendering this object
Required attributes:
Attributes:
id (str): A globally unique identifier for this object.
Generally a fully qualified name, including namespace.
@ -55,25 +50,21 @@ class PythonMapperBase:
children (list): Children of this object
parameters (list): Parameters to this object
methods (list): Methods on this object
Optional attributes:
"""
language = "base"
type = "base"
# Create a page in the output for this object.
top_level_object = False
_RENDER_LOG_LEVEL = "VERBOSE"
def __init__(self, obj, jinja_env, app, options=None):
def __init__(self, obj, jinja_env, app, url_root, options=None):
self.app = app
self.obj = obj
self.options = options
self.jinja_env = jinja_env
self.url_root = os.path.join("/", API_ROOT)
self.url_root = url_root
self.name = None
self.qual_name = None
self.id = None
def __getstate__(self):
@ -103,7 +94,7 @@ class PythonMapperBase:
def get_context_data(self):
own_page_level = self.app.config.autoapi_own_page_level
desired_page_level = _OWN_PAGE_LEVELS.index(own_page_level)
own_page_types = set(_OWN_PAGE_LEVELS[:desired_page_level+1])
own_page_types = set(_OWN_PAGE_LEVELS[: desired_page_level + 1])
return {
"autoapi_options": self.app.config.autoapi_options,
@ -127,28 +118,19 @@ class PythonMapperBase:
"""Shorten name property"""
return self.name.split(".")[-1]
@property
def pathname(self):
"""Sluggified path for filenames
def output_dir(self, root):
"""The directory to render this object."""
module = self.id[: -(len("." + self.qual_name))]
parts = [root] + module.split(".")
return pathlib.PurePosixPath(*parts)
Slugs to a filename using the follow steps
def output_filename(self):
"""The name of the file to render into, without a file suffix."""
filename = self.qual_name
if filename == "index":
filename = ".index"
* Decode unicode to approximate ascii
* Remove existing hyphens
* Substitute hyphens for non-word characters
* Break up the string as paths
"""
slug = self.name
slug = anyascii.anyascii(slug)
slug = slug.replace("-", "")
slug = re.sub(r"[^\w\.]+", "-", slug).strip("-")
return os.path.join(*slug.split("."))
def include_dir(self, root):
"""Return directory of file"""
parts = [root]
parts.extend(self.pathname.split(os.path.sep))
return "/".join(parts)
return filename
@property
def include_path(self):
@ -157,9 +139,7 @@ class PythonMapperBase:
This is used in ``toctree`` directives, as Sphinx always expects Unix
path separators
"""
parts = [self.include_dir(root=self.url_root)]
parts.append("index")
return "/".join(parts)
return str(self.output_dir(self.url_root) / self.output_filename())
@property
def display(self):
@ -185,7 +165,7 @@ class SphinxMapperBase:
app: Sphinx application instance
"""
def __init__(self, app, template_dir=None, url_root=None):
def __init__(self, app, template_dir=None, dir_root=None, url_root=None):
self.app = app
template_paths = [TEMPLATE_DIR]
@ -209,8 +189,9 @@ class SphinxMapperBase:
own_page_level = self.app.config.autoapi_own_page_level
desired_page_level = _OWN_PAGE_LEVELS.index(own_page_level)
self.own_page_types = set(_OWN_PAGE_LEVELS[:desired_page_level+1])
self.own_page_types = set(_OWN_PAGE_LEVELS[: desired_page_level + 1])
self.dir_root = dir_root
self.url_root = url_root
# Mapping of {filepath -> raw data}
@ -298,7 +279,8 @@ class SphinxMapperBase:
Args:
obj: Instance of a AutoAPI object
"""
if obj.type in self.own_page_types:
display = obj.display
if display and obj.type in self.own_page_types:
self.objects_to_render[obj.id] = obj
self.all_objects[obj.id] = obj
@ -306,6 +288,8 @@ class SphinxMapperBase:
while child_stack:
child = child_stack.pop()
self.all_objects[child.id] = child
if display and child.type in self.own_page_types:
self.objects_to_render[child.id] = child
child_stack.extend(getattr(child, "children", ()))
def map(self, options=None):
@ -327,51 +311,7 @@ class SphinxMapperBase:
"""
raise NotImplementedError
def output_child_rst(self, obj, obj_parent, detail_dir, source_suffix):
if not obj.display:
return
# Skip nested cases like functions in functions or clases in clases
if obj.type == obj_parent.type:
return
obj_child_page_level = _OWN_PAGE_LEVELS.index(obj.type)
desired_page_level = _OWN_PAGE_LEVELS.index(self.app.config.autoapi_own_page_level)
is_own_page = obj_child_page_level <= desired_page_level
if not is_own_page:
return
obj_child_rst = obj.render(
is_own_page=is_own_page,
)
if not obj_child_rst:
return
function_page_level = _OWN_PAGE_LEVELS.index("function")
is_level_beyond_function = function_page_level < desired_page_level
if obj.type in ["exception", "class"]:
if not is_level_beyond_function:
outfile = f"{obj.short_name}{source_suffix}"
path = os.path.join(detail_dir, outfile)
else:
outdir = os.path.join(detail_dir, obj.short_name)
ensuredir(outdir)
path = os.path.join(outdir, f"index{source_suffix}")
else:
is_parent_in_detail_dir = detail_dir.endswith(obj_parent.short_name)
outdir = detail_dir if is_parent_in_detail_dir else os.path.join(detail_dir, obj_parent.short_name)
ensuredir(outdir)
path = os.path.join(outdir, f"{obj.short_name}{source_suffix}")
with open(path, "wb+") as obj_child_detail_file:
obj_child_detail_file.write(obj_child_rst.encode("utf-8"))
for obj_child in obj.children:
child_detail_dir = os.path.join(detail_dir, obj.name)
self.output_child_rst(obj_child, obj, child_detail_dir, source_suffix)
def output_rst(self, root, source_suffix):
def output_rst(self, source_suffix):
for _, obj in status_iterator(
self.objects_to_render.items(),
colorize("bold", "[AutoAPI] ") + "Rendering Data... ",
@ -379,29 +319,24 @@ class SphinxMapperBase:
verbosity=1,
stringify_func=(lambda x: x[0]),
):
if not obj.display:
continue
rst = obj.render(is_own_page=True)
if not rst:
continue
detail_dir = obj.include_dir(root=root)
ensuredir(detail_dir)
path = os.path.join(detail_dir, f"index{source_suffix}")
output_dir = obj.output_dir(self.dir_root)
ensuredir(output_dir)
output_path = output_dir / obj.output_filename()
path = f"{output_path}{source_suffix}"
with open(path, "wb+") as detail_file:
detail_file.write(rst.encode("utf-8"))
for child in obj.children:
self.output_child_rst(child, obj, detail_dir, source_suffix)
if self.app.config.autoapi_add_toctree_entry:
self._output_top_rst(root)
self._output_top_rst()
def _output_top_rst(self, root):
def _output_top_rst(self):
# Render Top Index
top_level_index = os.path.join(root, "index.rst")
pages = self.objects_to_render.values()
top_level_index = os.path.join(self.dir_root, "index.rst")
pages = [obj for obj in self.objects_to_render.values() if obj.display]
with open(top_level_index, "wb") as top_level_file:
content = self.jinja_env.get_template("index.rst")
top_level_file.write(content.render(pages=pages).encode("utf-8"))

@ -62,12 +62,14 @@ def _expand_wildcard_placeholder(original_module, originals_map, placeholder):
placeholders = []
for original in originals:
new_full_name = placeholder["full_name"].replace("*", original["name"])
new_qual_name = placeholder["qual_name"].replace("*", original["name"])
new_original_path = placeholder["original_path"].replace("*", original["name"])
if "original_path" in original:
new_original_path = original["original_path"]
new_placeholder = dict(
placeholder,
name=original["name"],
qual_name=new_qual_name,
full_name=new_full_name,
original_path=new_original_path,
)
@ -167,6 +169,7 @@ def _resolve_placeholder(placeholder, original):
assert original["type"] != "placeholder"
# The name remains the same.
new["name"] = placeholder["name"]
new["qual_name"] = placeholder["qual_name"]
new["full_name"] = placeholder["full_name"]
# Record where the placeholder originally came from.
new["original_path"] = original["full_name"]
@ -217,7 +220,7 @@ def _link_objs(value):
class PythonSphinxMapper(SphinxMapperBase):
"""Auto API domain handler for Python
"""AutoAPI domain handler for Python
Parses directly from Python files.
@ -240,8 +243,8 @@ class PythonSphinxMapper(SphinxMapperBase):
)
}
def __init__(self, app, template_dir=None, url_root=None):
super().__init__(app, template_dir, url_root)
def __init__(self, app, template_dir=None, dir_root=None, url_root=None):
super().__init__(app, template_dir, dir_root, url_root)
self.jinja_env.filters["link_objs"] = _link_objs
self._use_implicit_namespace = (
@ -340,15 +343,33 @@ class PythonSphinxMapper(SphinxMapperBase):
visit_path = collections.OrderedDict()
_resolve_module_placeholders(modules, module_name, visit_path, resolved)
def _hide_yo_kids(self):
"""For all direct children of a module/package, hide them if needed."""
for module in self.paths.values():
if module["all"] is not None:
all_names = set(module["all"])
for child in module["children"]:
if child["qual_name"] not in all_names:
child["hide"] = True
elif module["type"] == "module":
for child in module["children"]:
if child.get("imported"):
child["hide"] = True
def map(self, options=None):
self._resolve_placeholders()
self._hide_yo_kids()
self.app.env.autoapi_annotations = {}
super().map(options)
top_level_objects = {obj.id: obj for obj in self.all_objects.values() if isinstance(obj, TopLevelPythonPythonMapper)}
top_level_objects = {
obj.id: obj
for obj in self.all_objects.values()
if isinstance(obj, TopLevelPythonPythonMapper)
}
parents = {obj.name: obj for obj in top_level_objects.values()}
for obj in self.objects_to_render.values():
for obj in top_level_objects.values():
parent_name = obj.name.rsplit(".", 1)[0]
if parent_name in parents and parent_name != obj.name:
parent = parents[parent_name]
@ -380,9 +401,9 @@ class PythonSphinxMapper(SphinxMapperBase):
options=self.app.config.autoapi_options,
jinja_env=self.jinja_env,
app=self.app,
url_root=self.url_root,
**kwargs,
)
obj.url_root = self.url_root
for child_data in data.get("children", []):
for child_obj in self.create_class(

@ -1,4 +1,5 @@
import functools
import pathlib
from typing import List, Optional
import sphinx.util.logging
@ -44,6 +45,7 @@ class PythonPythonMapper(PythonMapperBase):
super().__init__(obj, **kwargs)
self.name = obj["name"]
self.qual_name = obj["qual_name"]
self.id = obj.get("full_name", self.name)
# Optional
@ -56,6 +58,7 @@ class PythonPythonMapper(PythonMapperBase):
:type: bool
"""
self._hide = obj.get("hide", False)
# For later
self._class_content = class_content
@ -81,6 +84,16 @@ class PythonPythonMapper(PythonMapperBase):
self._docstring = value
self._docstring_resolved = True
@property
def is_top_level_object(self):
"""Whether this object is at the very top level (True) or not (False).
This will be False for subpackages and submodules.
:type: bool
"""
return "." not in self.id
@property
def is_undoc_member(self):
"""Whether this object has a docstring (False) or not (True).
@ -144,12 +157,17 @@ class PythonPythonMapper(PythonMapperBase):
self.is_special_member and "special-members" not in self.options
)
skip_imported_member = self.imported and "imported-members" not in self.options
skip_inherited_member = (
self.inherited and "inherited-members" not in self.options
)
return (
skip_undoc_member
self._hide
or skip_undoc_member
or skip_private_member
or skip_special_member
or skip_imported_member
or skip_inherited_member
)
def _ask_ignore(self, skip): # type: (bool) -> bool
@ -229,11 +247,10 @@ class PythonMethod(PythonFunction):
"""
def _should_skip(self): # type: () -> bool
skip = super()._should_skip() or self.name in (
return super()._should_skip() or self.name in (
"__new__",
"__init__",
)
return self._ask_ignore(skip)
class PythonProperty(PythonPythonMapper):
@ -300,14 +317,6 @@ class TopLevelPythonPythonMapper(PythonPythonMapper):
def __init__(self, obj, **kwargs):
super().__init__(obj, **kwargs)
self.top_level_object = "." not in self.name
"""Whether this object is at the very top level (True) or not (False).
This will be False for subpackages and submodules.
:type: bool
"""
self.subpackages = []
self.submodules = []
self.all = obj["all"]
@ -335,6 +344,15 @@ class TopLevelPythonPythonMapper(PythonPythonMapper):
"""
return self._children_of_type("class")
def output_dir(self, root):
"""The path to the file to render into, without a file suffix."""
parts = [root] + self.name.split(".")
return pathlib.PurePosixPath(*parts)
def output_filename(self):
"""The path to the file to render into, without a file suffix."""
return "index"
class PythonModule(TopLevelPythonPythonMapper):
"""The representation of a module."""

@ -15,11 +15,15 @@ def _prepare_docstring(doc):
class Parser:
def __init__(self):
self._name_stack = []
self._qual_name_stack = []
self._full_name_stack = []
self._encoding = None
def _get_qual_name(self, name):
return ".".join(self._qual_name_stack + [name])
def _get_full_name(self, name):
return ".".join(self._name_stack + [name])
return ".".join(self._full_name_stack + [name])
def _parse_file(self, file_path, condition):
directory, filename = os.path.split(file_path)
@ -91,6 +95,7 @@ class Parser:
data = {
"type": type_,
"name": target,
"qual_name": self._get_qual_name(target),
"full_name": self._get_full_name(target),
"doc": _prepare_docstring(doc),
"value": value,
@ -111,6 +116,7 @@ class Parser:
data = {
"type": type_,
"name": node.name,
"qual_name": self._get_qual_name(node.name),
"full_name": self._get_full_name(node.name),
"bases": basenames,
"doc": _prepare_docstring(astroid_utils.get_class_docstring(node)),
@ -119,7 +125,8 @@ class Parser:
"children": [],
}
self._name_stack.append(node.name)
self._qual_name_stack.append(node.name)
self._full_name_stack.append(node.name)
overridden = set()
overloads = {}
for base in itertools.chain(iter((node,)), node.ancestors()):
@ -148,7 +155,8 @@ class Parser:
overridden.update(seen)
self._name_stack.pop()
self._qual_name_stack.pop()
self._full_name_stack.pop()
return [data]
@ -185,6 +193,7 @@ class Parser:
data = {
"type": type_,
"name": node.name,
"qual_name": self._get_qual_name(node.name),
"full_name": self._get_full_name(node.name),
"args": astroid_utils.get_args_info(node.args),
"doc": _prepare_docstring(astroid_utils.get_func_docstring(node)),
@ -209,14 +218,19 @@ class Parser:
def _parse_local_import_from(self, node):
result = []
for name, alias in node.names:
is_wildcard = (alias or name) == "*"
full_name = self._get_full_name(alias or name)
original_path = astroid_utils.get_full_import_name(node, alias or name)
for import_name, alias in node.names:
is_wildcard = (alias or import_name) == "*"
original_path = astroid_utils.get_full_import_name(
node, alias or import_name
)
name = original_path if is_wildcard else (alias or import_name)
qual_name = self._get_qual_name(alias or import_name)
full_name = self._get_full_name(alias or import_name)
data = {
"type": "placeholder",
"name": original_path if is_wildcard else (alias or name),
"name": name,
"qual_name": qual_name,
"full_name": full_name,
"original_path": original_path,
}
@ -233,12 +247,13 @@ class Parser:
if node.package:
type_ = "package"
self._name_stack = [node.name]
self._full_name_stack = [node.name]
self._encoding = node.file_encoding
data = {
"type": type_,
"name": node.name,
"qual_name": node.name,
"full_name": node.name,
"doc": _prepare_docstring(node.doc_node.value if node.doc_node else ""),
"children": [],

@ -6,10 +6,8 @@ This page contains auto-generated API reference documentation [#f1]_.
.. toctree::
:titlesonly:
{% for page in pages %}
{% if page.top_level_object and page.display %}
{% for page in pages|selectattr("is_top_level_object") %}
{{ page.include_path }}
{% endif %}
{% endfor %}
.. [#f1] Created with `sphinx-autoapi <https://github.com/readthedocs/sphinx-autoapi>`_

@ -1,105 +1,104 @@
{% if obj.display %}
{% if is_own_page %}
{{ obj.name }}
{{ "=" * obj.name | length }}
{% endif %}
.. py:{{ obj.type }}:: {{ obj.short_name }}{% if obj.args %}({{ obj.args }}){% endif %}
{% if is_own_page %}
:class:`{{ obj.id }}`
========={{ "=" * obj.id | length }}
{% for (args, return_annotation) in obj.overloads %}
{{ " " * (obj.type | length) }} {{ obj.short_name }}{% if args %}({{ args }}){% endif %}
{% endif %}
{% set visible_children = obj.children|selectattr("display")|list %}
{% set own_page_children = visible_children|selectattr("type", "in", own_page_types)|list %}
{% if is_own_page and own_page_children %}
.. toctree::
:hidden:
{% for child in own_page_children %}
{{ child.include_path }}
{% endfor %}
{% endfor %}
{% endif %}
.. py:{{ obj.type }}:: {% if is_own_page %}{{ obj.id }}{% else %}{{ obj.short_name }}{% endif %}{% if obj.args %}({{ obj.args }}){% endif %}
{% for (args, return_annotation) in obj.overloads %}
{{ " " * (obj.type | length) }} {{ obj.short_name }}{% if args %}({{ args }}){% endif %}
{% endfor %}
{% if obj.bases %}
{% if "show-inheritance" in autoapi_options %}
{% if "show-inheritance" in autoapi_options %}
Bases: {% for base in obj.bases %}{{ base|link_objs }}{% if not loop.last %}, {% endif %}{% endfor %}
{% endif %}
{% endif %}
{% if "show-inheritance-diagram" in autoapi_options and obj.bases != ["object"] %}
{% if "show-inheritance-diagram" in autoapi_options and obj.bases != ["object"] %}
.. autoapi-inheritance-diagram:: {{ obj.obj["full_name"] }}
:parts: 1
{% if "private-members" in autoapi_options %}
{% if "private-members" in autoapi_options %}
:private-bases:
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% endif %}
{% if obj.docstring %}
{{ obj.docstring|indent(3) }}
{% endif %}
{# TODO: Rendering of all children below this line must be conditional on own_page_types #}
{% if "inherited-members" in autoapi_options %}
{% set visible_classes = obj.classes|selectattr("display")|list %}
{% else %}
{% set visible_classes = obj.classes|rejectattr("inherited")|selectattr("display")|list %}
{% endif %}
{% for klass in visible_classes %}
{{ klass.render(own_page_types=[])|indent(3) }}
{% for obj_item in visible_children %}
{% if obj_item.type not in own_page_types %}
{{ obj_item.render()|indent(3) }}
{% endif %}
{% endfor %}
{% if "inherited-members" in autoapi_options %}
{% set visible_properties = obj.properties|selectattr("display")|list %}
{% else %}
{% set visible_properties = obj.properties|rejectattr("inherited")|selectattr("display")|list %}
{% endif %}
{% if "property" in own_page_types and visible_properties %}
{% if is_own_page and own_page_children %}
{% set visible_attributes = own_page_children|selectattr("type", "equalto", "attribute")|list %}
{% if visible_attributes %}
Attributes
----------
Properties
----------
.. autoapisummary::
.. toctree::
:hidden:
{% for attribute in visible_attributes %}
{{ attribute.id }}
{% endfor %}
{% for property in visible_properties %}
{{ property.name }}
{% endfor %}
{% else %}
{% for property in visible_properties %}
{{ property.render()|indent(3) }}
{% endfor %}
{% endif %}
{% if "inherited-members" in autoapi_options %}
{% set visible_attributes = obj.attributes|selectattr("display")|list %}
{% else %}
{% set visible_attributes = obj.attributes|rejectattr("inherited")|selectattr("display")|list %}
{% endif %}
{% if "attribute" in own_page_types and visible_attributes %}
Attributes
----------
{% endif %}
{% set visible_exceptions = own_page_children|selectattr("type", "equalto", "exception")|list %}
{% if visible_exceptions %}
Exceptions
----------
.. toctree::
:hidden:
.. autoapisummary::
{% for attribute in visible_attributes %}
{{ attribute.name }}
{% endfor %}
{% else %}
{% for attribute in visible_attributes %}
{{ attribute.render()|indent(3) }}
{% endfor %}
{% endif %}
{% if "inherited-members" in autoapi_options %}
{% set visible_methods = obj.methods|selectattr("display")|list %}
{% else %}
{% set visible_methods = obj.methods|rejectattr("inherited")|selectattr("display")|list %}
{% endif %}
{% if "method" in own_page_types and visible_methods %}
{% for exception in visible_exceptions %}
{{ exception.id }}
{% endfor %}
Methods
-------
.. toctree::
:hidden:
{% endif %}
{% set visible_classes = own_page_children|selectattr("type", "equalto", "class")|list %}
{% if visible_classes %}
Classes
-------
{% for method in visible_methods %}
{{ method.name }}
{% endfor %}
{% else %}
{% for method in visible_methods %}
{{ method.render()|indent(3) }}
{% endfor %}
.. autoapisummary::
{% for klass in visible_classes %}
{{ klass.id }}
{% endfor %}
{% endif %}
{% set visible_methods = own_page_children|selectattr("type", "equalto", "method")|list %}
{% if visible_methods %}
Methods
-------
.. autoapisummary::
{% for method in visible_methods %}
{{ method.id }}
{% endfor %}
{% endif %}
{% endif %}
{% endif %}

@ -1,42 +1,42 @@
{% if obj.display %}
{% if is_own_page %}
{{ obj.name }}
{{ "=" * obj.name | length }}
{% if is_own_page %}
:py:{{ obj.type|truncate(4, True, "", 0) }}:`{{ obj.id }}`
==========={{ "=" * obj.id | length }}
{% endif %}
.. py:{{ obj.type }}:: {{ obj.name }}
{%- if obj.annotation is not none %}
:type: {%- if obj.annotation %} {{ obj.annotation }}{%- endif %}
{%- endif %}
{% endif %}
.. py:{{ obj.type }}:: {% if is_own_page %}{{ obj.id }}{% else %}{{ obj.name }}{% endif %}
{% if obj.annotation is not none %}
{%- if obj.value is not none %}
:type: {% if obj.annotation %} {{ obj.annotation }}{% endif %}
{% endif %}
{% if obj.value is not none %}
:value: {% if obj.value is string and obj.value.splitlines()|count > 1 -%}
Multiline-String
{% if obj.value is string and obj.value.splitlines()|count > 1 %}
:value: Multiline-String
.. raw:: html
.. raw:: html
<details><summary>Show Value</summary>
<details><summary>Show Value</summary>
.. code-block:: python
.. code-block:: python
"""{{ obj.value|indent(width=8,blank=true) }}"""
"""{{ obj.value|indent(width=6,blank=true) }}"""
.. raw:: html
.. raw:: html
</details>
</details>
{%- else -%}
{%- if obj.value is string -%}
{{ "%r" % obj.value|string|truncate(100) }}
{%- else -%}
{{ obj.value|string|truncate(100) }}
{%- endif -%}
{%- endif %}
{%- endif %}
{% else %}
{% if obj.value is string %}
:value: {{ "%r" % obj.value|string|truncate(100) }}
{% else %}
:value: {{ obj.value|string|truncate(100) }}
{% endif %}
{% endif %}
{% endif %}
{% if obj.docstring %}
{{ obj.docstring|indent(3) }}
{% endif %}
{% endif %}

@ -1,20 +1,21 @@
{% if obj.display %}
{% if is_own_page %}
{{ obj.name }}
{{ "=" * obj.name | length }}
{% if is_own_page %}
:py:func:`{{ obj.id }}`
==========={{ "=" * obj.id | length }}
{% endif %}
.. py:function:: {{ obj.short_name }}({{ obj.args }}){% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %}
{% for (args, return_annotation) in obj.overloads %}
{{ obj.short_name }}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %}
{% endif %}
.. py:function:: {% if is_own_page %}{{ obj.id }}{% else %}{{ obj.short_name }}{% endif %}({{ obj.args }}){% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %}
{% for (args, return_annotation) in obj.overloads %}
{% endfor %}
{%+ if is_own_page %}{{ obj.id }}{% else %}{{ obj.short_name }}{% endif %}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %}
{% endfor %}
{% for property in obj.properties %}
:{{ property }}:
{% endfor %}
{% if obj.docstring %}
{{ obj.docstring|indent(3) }}
{% endif %}
{% endif %}

@ -1,24 +1,21 @@
{%- if obj.display %}
{% if is_own_page %}
{{ obj.name }}
{{ "=" * obj.name | length }}
{% if obj.display %}
{% if is_own_page %}
:py:meth:`{{ obj.id }}`
==========={{ "=" * obj.id | length }}
{% endif %}
.. py:method:: {{ obj.short_name }}({{ obj.args }}){% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %}
{% for (args, return_annotation) in obj.overloads %}
{{ obj.short_name }}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %}
{% endif %}
.. py:method:: {% if is_own_page %}{{ obj.id }}{% else %}{{ obj.short_name }}{% endif %}({{ obj.args }}){% if obj.return_annotation is not none %} -> {{ obj.return_annotation }}{% endif %}
{% for (args, return_annotation) in obj.overloads %}
{% endfor %}
{% if obj.properties %}
{%+ if is_own_page %}{{ obj.id }}{% else %}{{ obj.short_name }}{% endif %}({{ args }}){% if return_annotation is not none %} -> {{ return_annotation }}{% endif %}
{% endfor %}
{% for property in obj.properties %}
:{{ property }}:
{% endfor %}
{% else %}
{% endif %}
{% if obj.docstring %}
{{ obj.docstring|indent(3) }}
{% endif %}
{% endif %}

@ -1,90 +1,67 @@
{% if obj.display %}
{% if is_own_page %}
:py:mod:`{{ obj.name }}`
=========={{ "=" * obj.name|length }}
:py:mod:`{{ obj.id }}`
=========={{ "=" * obj.id|length }}
{% endif %}
.. py:module:: {{ obj.name }}
{% if obj.docstring %}
{% if obj.docstring %}
.. autoapi-nested-parse::
{{ obj.docstring|indent(3) }}
{% endif %}
{% endif %}
{% block subpackages %}
{% set visible_subpackages = obj.subpackages|selectattr("display")|list %}
{% if visible_subpackages %}
{% block subpackages %}
{% set visible_subpackages = obj.subpackages|selectattr("display")|list %}
{% if visible_subpackages %}
Subpackages
-----------
.. toctree::
:hidden:
{% for subpackage in visible_subpackages %}
{{ subpackage.short_name }}/index.rst
{% endfor %}
.. autoapisummary::
.. toctree::
:maxdepth: 1
{% for subpackage in visible_subpackages %}
{{ subpackage.id }}
{% endfor %}
{% for subpackage in visible_subpackages %}
{{ subpackage.include_path }}
{% endfor %}
{% endif %}
{% endblock %}
{% block submodules %}
{% set visible_submodules = obj.submodules|selectattr("display")|list %}
{% if visible_submodules %}
{% if "module" in own_page_types %}
{% endif %}
{% endblock %}
{% block submodules %}
{% set visible_submodules = obj.submodules|selectattr("display")|list %}
{% if visible_submodules %}
Submodules
----------
.. toctree::
:hidden:
:maxdepth: 1
{% for submodule in visible_submodules %}
{{ submodule.short_name }}/index.rst
{{ submodule.include_path }}
{% endfor %}
.. autoapisummary::
{% for submodule in visible_submodules %}
{{ submodule.id }}
{% endfor %}
{% else %}
{% for submodule in visible_submodules %}
{{ submodule.render() }}
{% endfor %}
{% endif %}
{% endif %}
{% endblock %}
{% block content %}
{% if obj.all is not none %}
{% set visible_children = obj.children|selectattr("short_name", "in", obj.all)|list %}
{% elif obj.type is equalto("package") %}
{% endblock %}
{% block content %}
{% set visible_children = obj.children|selectattr("display")|list %}
{% else %}
{% set visible_children = obj.children|selectattr("display")|rejectattr("imported")|list %}
{% endif %}
{% if visible_children %}
{% if is_own_page %}
{% if visible_children %}
{% set visible_attributes = visible_children|selectattr("type", "equalto", "data")|list %}
{% if visible_attributes %}
{% if "attribute" in own_page_types or "show-module-summary" in autoapi_options %}
Attributes
----------
{% if "attribute" in own_page_types %}
.. toctree::
:hidden:
{% for attribute in visible_attributes %}
{{ attribute.short_name }}
{{ attribute.include_path }}
{% endfor %}
{% endif%}
{% endif %}
.. autoapisummary::
{% for attribute in visible_attributes %}
@ -99,12 +76,13 @@ Attributes
{% if "exception" in own_page_types or "show-module-summary" in autoapi_options %}
Exceptions
----------
{% if "exception" in own_page_types %}
.. toctree::
:hidden:
{% for exception in visible_exceptions %}
{{ exception.short_name }}/index.rst
{{ exception.include_path }}
{% endfor %}
{% endif %}
@ -122,23 +100,13 @@ Exceptions
{% if "class" in own_page_types or "show-module-summary" in autoapi_options %}
Classes
-------
{% if "class" in own_page_types %}
.. toctree::
:hidden:
{% for klass in visible_classes %}
{#
The set own_page_types sometimes is not ordered! This changes the value of
its last element. Thus, the best way to check is to verify if 'function'
lies within the list
Do -> if 'function' not in own_page_types
Instead of -> if "class" == (own_page_types | list | last)
#}
{% if "method" not in own_page_types %}
{{ klass.short_name }}.rst
{% else %}
{{ klass.short_name }}/index.rst
{% endif %}
{{ klass.include_path }}
{% endfor %}
{% endif %}
@ -156,12 +124,13 @@ Classes
{% if "function" in own_page_types or "show-module-summary" in autoapi_options %}
Functions
---------
{% if "function" in own_page_types %}
.. toctree::
:hidden:
{% for function in visible_functions %}
{{ function.short_name }}.rst
{{ function.include_path }}
{% endfor %}
{% endif %}
@ -174,21 +143,28 @@ Functions
{% endif %}
{% set this_page_children = visible_children|rejectattr("type", "in", own_page_types)|list %}
{% if this_page_children %}
{{ obj.type|title }} Contents
{{ "-" * obj.type|length }}---------
{% for obj_item in visible_children %}
{% if obj_item.type not in own_page_types %}
{% for obj_item in this_page_children %}
{{ obj_item.render()|indent(0) }}
{% endif %}
{% endfor %}
{% else %}
{# If this is not its own page, the children won't have their own page either. #}
{# So render them as normal, without needing to check if they have their own page. #}
{% for obj_item in visible_children %}
{{ obj_item.render()|indent(3) }}
{% endfor %}
{% endfor %}
{% endif %}
{% endif %}
{% endblock %}
{% else %}
.. py:module:: {{ obj.name }}
{% if obj.docstring %}
.. autoapi-nested-parse::
{{ obj.docstring|indent(6) }}
{% endif %}
{% endblock %}
{% for obj_item in visible_children %}
{{ obj_item.render()|indent(3) }}
{% endfor %}
{% endif %}
{% endif %}

@ -1,18 +1,18 @@
{%- if obj.display %}
{% if is_own_page %}
{{ obj.name }}
{{ "=" * obj.name | length }}
{% if obj.display %}
{% if is_own_page %}
:py:property:`{{ obj.id }}`
==============={{ "=" * obj.id | length }}
{% endif %}
.. py:property:: {{ obj.short_name }}
{% endif %}
.. py:property:: {% if is_own_page %}{{ obj.id}}{% else %}{{ obj.short_name }}{% endif %}
{% if obj.annotation %}
:type: {{ obj.annotation }}
{% endif %}
{% if obj.properties %}
{% for property in obj.properties %}
:{{ property }}:
{% endfor %}
{% endif %}
{% if obj.docstring %}
{{ obj.docstring|indent(3) }}

@ -0,0 +1 @@
Fix emitting ignore event twice for methods.

@ -0,0 +1 @@
Objects can render to their own page

@ -2,7 +2,7 @@ How-to Guides
=============
These guides will take you through the steps to perform common actions
or solve common problems in AutoAPI.
or solve common problems in AutoAPI.
They will assume that you already have a Sphinx project with AutoAPI
set up already.
If you don't know how to do this then read the :doc:`tutorials`.

@ -79,11 +79,9 @@ Customisation Options
and requires `Graphviz <https://graphviz.org/>`_ to be installed.
* ``show-module-summary``: Whether to include autosummary directives
in generated module documentation.
* ``imported-members``: Display objects imported from the same
top level package or module.
The default module template does not include imported objects,
even with this option enabled.
The default package template does.
* ``imported-members``: For objects imported into a package,
display objects imported from the same top level package or module.
This option does not effect objects imported into a module.
.. confval:: autoapi_ignore
@ -189,19 +187,16 @@ Customisation Options
a single page. Valid levels, in descending order of hierarchy, are as
follows:
* Package
* ``module``: Packages, modules, subpackages, and submodules.
* Module
* ``class``: Classes, exceptions, and all object types mentioned above.
* Class
* ``function``: Functions, and all object types mentioned above.
* Function
* ``method``: Methods, and all object types mentioned above.
* Method
* Attribute
* Data
* ``attribute``: Class and module level attributes, properties,
and all object types mentioned above.
Events

@ -10,11 +10,6 @@ ignore_missing_imports = true
module = "autoapi.documenters"
ignore_errors = true
[[tool.mypy.overrides]]
# https://github.com/anyascii/anyascii/issues/19
module = "anyascii"
ignore_missing_imports = true
[tool.ruff.lint.pydocstyle]
convention = "google"

@ -33,7 +33,6 @@ packages = find:
include_package_data = True
python_requires = >=3.8
install_requires =
anyascii
astroid>=2.7;python_version<"3.12"
astroid>=3.0.0a1;python_version>="3.12"
Jinja2

@ -0,0 +1,68 @@
import io
import os
import pathlib
import shutil
from unittest.mock import call
from bs4 import BeautifulSoup
import pytest
from sphinx.application import Sphinx
@pytest.fixture(scope="session")
def rebuild():
def _rebuild(confdir=".", **kwargs):
app = Sphinx(
srcdir=".",
confdir=confdir,
outdir="_build/html",
doctreedir="_build/.doctrees",
buildername="html",
pdb=True,
**kwargs,
)
app.build()
return _rebuild
@pytest.fixture(scope="class")
def builder(rebuild):
cwd = os.getcwd()
def build(test_dir, **kwargs):
if kwargs.get("warningiserror"):
# Add any warnings raised when using `Sphinx` more than once
# in a Python session.
confoverrides = kwargs.setdefault("confoverrides", {})
confoverrides.setdefault("suppress_warnings", [])
suppress = confoverrides["suppress_warnings"]
suppress.append("app.add_node")
suppress.append("app.add_directive")
suppress.append("app.add_role")
os.chdir("tests/python/{0}".format(test_dir))
rebuild(**kwargs)
yield build
try:
shutil.rmtree("_build")
if (pathlib.Path("autoapi") / "index.rst").exists():
shutil.rmtree("autoapi")
finally:
os.chdir(cwd)
@pytest.fixture(scope="class")
def parse():
cache = {}
def parser(path):
if path not in cache:
with io.open(path, encoding="utf8") as file_handle:
cache[path] = BeautifulSoup(file_handle, features="html.parser")
return cache[path]
yield parser

@ -17,3 +17,4 @@ htmlhelp_basename = "pyexampledoc"
extensions = ["sphinx.ext.autodoc", "autoapi.extension"]
autoapi_dirs = ["example"]
autoapi_file_pattern = "*.py"
autoapi_keep_files = True

@ -15,6 +15,6 @@ todo_include_todos = False
html_theme = "alabaster"
htmlhelp_basename = "pypackageexampledoc"
extensions = ["autoapi.extension"]
autoapi_dirs = ["example"]
autoapi_dirs = ["package"]
autoapi_file_pattern = "*.py"
autoapi_keep_files = True

@ -1,8 +0,0 @@
"""This is a docstring."""
from . import foo
def module_level_function(foo, bar):
"""A module level method"""
pass

@ -0,0 +1,42 @@
"""This is a docstring."""
from . import submodule
from .subpackage.submodule import function as aliased_function
from .subpackage.submodule import not_in_all_function
__all__ = (
"aliased_function",
"Class",
"DATA",
"function",
"MyException",
)
DATA = 42
def function(foo, bar):
"""A module level function"""
class Class(object):
"""This is a class."""
class_var = 42
"""Class var docstring"""
class NestedClass(object):
"""A nested class just to test things out"""
@classmethod
def a_classmethod():
"""A class method"""
return True
def method_okay(self, foo=None, bar=None):
"""This method should parse okay"""
return True
class MyException(Exception):
"""This is an exception."""

@ -3,21 +3,36 @@
This is a description
"""
MODULE_DATA = 42
from .subpackage.submodule import function as aliased_function
from .subpackage.submodule import not_in_all_function
__all__ = (
"aliased_function",
"Class",
"DATA",
"function",
"MyException",
)
class Foo(object):
class_var = 42 #: Class var docstring
DATA = 42
another_class_var = 42
"""Another class var docstring"""
class Meta(object):
def function(foo, bar):
"""A module level function"""
class Class(object):
"""This is a class."""
class_var = 42
"""Class var docstring"""
class NestedClass(object):
"""A nested class just to test things out"""
@classmethod
def foo():
"""The foo class method"""
def a_classmethod():
"""A class method"""
return True
def method_okay(self, foo=None, bar=None):
@ -63,3 +78,7 @@ class Foo(object):
int: The sum of foo and bar.
"""
return foo + bar
class MyException(Exception):
"""This is an exception."""

@ -0,0 +1,13 @@
"""This is a docstring."""
from .submodule import function as aliased_function
from .submodule import not_in_all_function
__all__ = (
"aliased_function",
"function",
)
def function(foo, bar):
"""A module level function"""

@ -0,0 +1,41 @@
"""Example module
This is a description
"""
DATA = 42
def function(foo, bar):
"""A module level function"""
def _private_function():
"""A function that shouldn't get rendered."""
def not_in_all_function():
"""A function that doesn't exist in __all__ when imported."""
class Class(object):
"""This is a class."""
class_var = 42
"""Class var docstring"""
class NestedClass(object):
"""A nested class just to test things out"""
@classmethod
def a_classmethod():
"""A class method"""
return True
def method_okay(self, foo=None, bar=None):
"""This method should parse okay"""
return True
class MyException(Exception):
"""This is an exception."""

@ -0,0 +1,997 @@
import os
import pytest
class TestModule:
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={
"autoapi_own_page_level": "module",
"autoapi_options": [
"members",
"undoc-members",
"show-inheritance",
"imported-members",
],
},
)
def test_package(self, parse):
package_path = "_build/html/autoapi/package/index.html"
package_file = parse(package_path)
docstring = package_file.find("p")
assert docstring.text == "This is a docstring."
# There should be links to the children with their own page
subpackages = package_file.find(id="subpackages")
assert subpackages
assert subpackages.find("a", string="package.subpackage")
submodules = package_file.find(id="submodules")
assert submodules
assert submodules.find("a", string="package.submodule")
# There should not be links to the children without their own page
assert not package_file.find(id="attributes")
assert not package_file.find(id="exceptions")
assert not package_file.find(id="classes")
assert not package_file.find(id="functions")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
contents = package_file.find(id="package-contents")
assert contents.find(id="package.DATA")
assert contents.find(id="package.MyException")
assert contents.find(id="package.Class")
assert contents.find(id="package.Class.class_var")
assert contents.find(id="package.Class.NestedClass")
assert contents.find(id="package.Class.method_okay")
assert contents.find(id="package.Class.NestedClass")
assert contents.find(id="package.Class.NestedClass.a_classmethod")
assert contents.find(id="package.function")
assert contents.find(id="package.aliased_function")
# Hidden children are never rendered.
assert not contents.find(id="package.not_in_all_function")
def test_subpackage(self, parse):
subpackage_path = "_build/html/autoapi/package/subpackage/index.html"
subpackage_file = parse(subpackage_path)
docstring = subpackage_file.find("p")
assert docstring.text == "This is a docstring."
# There should be links to the children with their own page
assert not subpackage_file.find(id="subpackages")
submodules = subpackage_file.find(id="submodules")
assert submodules
assert submodules.find("a", string="package.subpackage.submodule")
# There should not be links to the children without their own page
assert not subpackage_file.find(id="attributes")
assert not subpackage_file.find(id="exceptions")
assert not subpackage_file.find(id="classes")
assert not subpackage_file.find(id="functions")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
contents = subpackage_file.find(id="package-contents")
assert contents.find(id="package.subpackage.function")
assert contents.find(id="package.subpackage.aliased_function")
# Hidden children are never rendered.
assert not contents.find(id="package.subpackage.not_in_all_function")
def test_module(self, parse):
submodule_path = "_build/html/autoapi/package/submodule/index.html"
submodule_file = parse(submodule_path)
docstring = submodule_file.find("p")
assert docstring.text == "Example module"
# There should be links to the children with their own page
pass # there are no children with their own page
# There should not be links to the children without their own page
assert not submodule_file.find(id="submodules")
assert not submodule_file.find(id="subpackages")
assert not submodule_file.find(id="attributes")
assert not submodule_file.find(id="exceptions")
assert not submodule_file.find(id="classes")
assert not submodule_file.find(id="functions")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
contents = submodule_file.find(id="module-contents")
assert contents.find(id="package.submodule.DATA")
assert contents.find(id="package.submodule.MyException")
assert contents.find(id="package.submodule.Class")
assert contents.find(id="package.submodule.Class.class_var")
assert contents.find(id="package.submodule.Class.NestedClass")
assert contents.find(id="package.submodule.Class.method_okay")
assert contents.find(id="package.submodule.Class.NestedClass")
assert contents.find(id="package.submodule.Class.NestedClass.a_classmethod")
assert contents.find(id="package.submodule.function")
assert contents.find(id="package.submodule.aliased_function")
# Hidden children are never rendered.
assert not contents.find(id="package.submodule.not_in_all_function")
def test_rendered_only_expected_pages(self):
_, dirs, files = next(os.walk("_build/html/autoapi/package"))
assert sorted(dirs) == ["submodule", "subpackage"]
assert files == ["index.html"]
_, dirs, files = next(os.walk("_build/html/autoapi/package/submodule"))
assert not dirs
assert files == ["index.html"]
_, dirs, files = next(os.walk("_build/html/autoapi/package/subpackage"))
assert dirs == ["submodule"]
assert files == ["index.html"]
_, dirs, files = next(
os.walk("_build/html/autoapi/package/subpackage/submodule")
)
assert not dirs
assert files == ["index.html"]
def test_index(self, parse):
index_path = "_build/html/autoapi/index.html"
index_file = parse(index_path)
top_links = index_file.find_all(class_="toctree-l1")
top_hrefs = sorted(link.a["href"] for link in top_links)
assert top_hrefs == [
"#",
"package/index.html",
]
class TestClass:
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={
"autoapi_own_page_level": "class",
"autoapi_options": [
"members",
"undoc-members",
"show-inheritance",
"imported-members",
],
},
)
def test_package(self, parse):
package_path = "_build/html/autoapi/package/index.html"
package_file = parse(package_path)
docstring = package_file.find("p")
assert docstring.text == "This is a docstring."
# There should be links to the children with their own page
subpackages = package_file.find(id="subpackages")
assert subpackages
assert subpackages.find("a", string="package.subpackage")
submodules = package_file.find(id="submodules")
assert submodules
assert submodules.find("a", string="package.submodule")
exceptions = package_file.find(id="exceptions")
assert exceptions
assert exceptions.find("a", title="package.MyException")
classes = package_file.find(id="classes")
assert classes
assert classes.find("a", title="package.Class")
assert not classes.find("a", title="package.Class.NestedClass")
# There should not be links to the children without their own page
assert not package_file.find(id="attributes")
assert not package_file.find(id="functions")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
contents = package_file.find(id="package-contents")
assert contents.find(id="package.DATA")
assert not contents.find(id="package.MyException")
assert not contents.find(id="package.Class")
assert not contents.find(id="package.Class.class_var")
assert not contents.find(id="package.Class.NestedClass")
assert not contents.find(id="package.Class.method_okay")
assert not contents.find(id="package.Class.NestedClass")
assert not contents.find(id="package.Class.NestedClass.a_classmethod")
assert contents.find(id="package.function")
assert contents.find(id="package.aliased_function")
# Hidden children are never rendered.
assert not contents.find(id="package.not_in_all_function")
def test_module(self, parse):
submodule_path = "_build/html/autoapi/package/submodule/index.html"
submodule_file = parse(submodule_path)
docstring = submodule_file.find("p")
assert docstring.text == "Example module"
# There should be links to the children with their own page
exceptions = submodule_file.find(id="exceptions")
assert exceptions
assert exceptions.find("a", title="package.submodule.MyException")
classes = submodule_file.find(id="classes")
assert classes
assert classes.find("a", title="package.submodule.Class")
assert not classes.find("a", title="package.submodule.Class.NestedClass")
# There should not be links to the children without their own page
assert not submodule_file.find(id="submodules")
assert not submodule_file.find(id="subpackages")
assert not submodule_file.find(id="attributes")
assert not submodule_file.find(id="functions")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
contents = submodule_file.find(id="module-contents")
assert contents.find(id="package.submodule.DATA")
assert not contents.find(id="package.submodule.MyException")
assert not contents.find(id="package.submodule.Class")
assert not contents.find(id="package.submodule.Class.class_var")
assert not contents.find(id="package.submodule.Class.NestedClass")
assert not contents.find(id="package.submodule.Class.method_okay")
assert not contents.find(id="package.submodule.Class.NestedClass")
assert not contents.find(id="package.submodule.Class.NestedClass.a_classmethod")
assert contents.find(id="package.submodule.function")
assert contents.find(id="package.submodule.aliased_function")
# Hidden children are never rendered.
assert not contents.find(id="package.submodule.not_in_all_function")
def test_class(self, parse):
class_path = "_build/html/autoapi/package/Class.html"
class_file = parse(class_path)
class_sig = class_file.find(id="package.Class")
assert class_sig
class_ = class_sig.parent
docstring = class_.find_all("p")[1]
assert docstring.text == "This is a class."
# There should be links to the children with their own page
classes = class_file.find(id="classes")
assert classes
assert classes.find("a", title="package.Class.NestedClass")
# There should not be links to the children without their own page
assert not class_file.find(id="attributes")
assert not class_file.find(id="exceptions")
assert not class_file.find(id="methods")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
assert class_.find(id="package.Class.class_var")
assert class_.find(id="package.Class.method_okay")
nested_class_path = "_build/html/autoapi/package/Class.NestedClass.html"
nested_class_file = parse(nested_class_path)
nested_class_sig = nested_class_file.find(id="package.Class.NestedClass")
assert nested_class_sig
nested_class = nested_class_sig.parent
# There should be links to the children with their own page
pass # there are no children with their own page
# There should not be links to the children without their own page
assert not class_file.find(id="attributes")
assert not class_file.find(id="exceptions")
assert not class_file.find(id="methods")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
assert nested_class.find(id="package.Class.NestedClass.a_classmethod")
def test_exception(self, parse):
exception_path = "_build/html/autoapi/package/MyException.html"
exception_file = parse(exception_path)
exception_sig = exception_file.find(id="package.MyException")
assert exception_sig
exception = exception_sig.parent
docstring = exception.find_all("p")[1]
assert docstring.text == "This is an exception."
# There should be links to the children with their own page
pass # there are no children with their own page
# There should not be links to the children without their own page
assert not exception_file.find(id="attributes")
assert not exception_file.find(id="exceptions")
assert not exception_file.find(id="methods")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
pass # there are no children without their own page
def test_rendered_only_expected_pages(self):
_, dirs, files = next(os.walk("_build/html/autoapi/package"))
assert sorted(dirs) == ["submodule", "subpackage"]
assert sorted(files) == [
"Class.NestedClass.html",
"Class.html",
"MyException.html",
"index.html",
]
_, dirs, files = next(os.walk("_build/html/autoapi/package/submodule"))
assert not dirs
assert sorted(files) == [
"Class.NestedClass.html",
"Class.html",
"MyException.html",
"index.html",
]
_, dirs, files = next(os.walk("_build/html/autoapi/package/subpackage"))
assert dirs == ["submodule"]
assert files == ["index.html"]
_, dirs, files = next(
os.walk("_build/html/autoapi/package/subpackage/submodule")
)
assert not dirs
assert sorted(files) == [
"Class.NestedClass.html",
"Class.html",
"MyException.html",
"index.html",
]
def test_index(self, parse):
index_path = "_build/html/autoapi/index.html"
index_file = parse(index_path)
top_links = index_file.find_all(class_="toctree-l1")
top_hrefs = sorted(link.a["href"] for link in top_links)
assert top_hrefs == [
"#",
"package/index.html",
]
class TestFunction:
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={
"autoapi_own_page_level": "function",
"autoapi_options": [
"members",
"undoc-members",
"show-inheritance",
"imported-members",
],
},
)
def test_package(self, parse):
package_path = "_build/html/autoapi/package/index.html"
package_file = parse(package_path)
docstring = package_file.find("p")
assert docstring.text == "This is a docstring."
# There should be links to the children with their own page
subpackages = package_file.find(id="subpackages")
assert subpackages
assert subpackages.find("a", string="package.subpackage")
submodules = package_file.find(id="submodules")
assert submodules
assert submodules.find("a", string="package.submodule")
classes = package_file.find(id="classes")
assert classes
assert classes.find("a", title="package.Class")
exceptions = package_file.find(id="exceptions")
assert exceptions
assert exceptions.find("a", title="package.MyException")
functions = package_file.find(id="functions")
assert functions
assert functions.find("a", title="package.function")
# There should not be links to the children without their own page
assert not package_file.find(id="attributes")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
contents = package_file.find(id="package-contents")
assert contents.find(id="package.DATA")
assert not contents.find(id="package.MyException")
assert not contents.find(id="package.Class")
assert not contents.find(id="package.Class.class_var")
assert not contents.find(id="package.Class.NestedClass")
assert not contents.find(id="package.Class.method_okay")
assert not contents.find(id="package.Class.NestedClass")
assert not contents.find(id="package.Class.NestedClass.a_classmethod")
assert not contents.find(id="package.function")
assert not contents.find(id="package.aliased_function")
# Hidden children are never rendered.
assert not contents.find(id="package.not_in_all_function")
def test_module(self, parse):
submodule_path = "_build/html/autoapi/package/submodule/index.html"
submodule_file = parse(submodule_path)
docstring = submodule_file.find("p")
assert docstring.text == "Example module"
# There should be links to the children with their own page
exceptions = submodule_file.find(id="exceptions")
assert exceptions
assert exceptions.find("a", title="package.submodule.MyException")
classes = submodule_file.find(id="classes")
assert classes
assert classes.find("a", title="package.submodule.Class")
assert not classes.find("a", title="package.submodule.Class.NestedClass")
functions = submodule_file.find(id="functions")
assert functions
assert functions.find("a", title="package.submodule.function")
# There should not be links to the children without their own page
assert not submodule_file.find(id="submodules")
assert not submodule_file.find(id="subpackages")
assert not submodule_file.find(id="attributes")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
contents = submodule_file.find(id="module-contents")
assert contents.find(id="package.submodule.DATA")
assert not contents.find(id="package.submodule.MyException")
assert not contents.find(id="package.submodule.Class")
assert not contents.find(id="package.submodule.Class.class_var")
assert not contents.find(id="package.submodule.Class.NestedClass")
assert not contents.find(id="package.submodule.Class.method_okay")
assert not contents.find(id="package.submodule.Class.NestedClass")
assert not contents.find(id="package.submodule.Class.NestedClass.a_classmethod")
assert not contents.find(id="package.submodule.function")
assert not contents.find(id="package.submodule.aliased_function")
# Hidden children are never rendered.
assert not contents.find(id="package.submodule.not_in_all_function")
def test_class(self, parse):
class_path = "_build/html/autoapi/package/Class.html"
class_file = parse(class_path)
class_sig = class_file.find(id="package.Class")
assert class_sig
class_ = class_sig.parent
docstring = class_.find_all("p")[1]
assert docstring.text == "This is a class."
# There should be links to the children with their own page
classes = class_file.find(id="classes")
assert classes
assert classes.find("a", title="package.Class.NestedClass")
# There should not be links to the children without their own page
assert not class_file.find(id="attributes")
assert not class_file.find(id="exceptions")
assert not class_file.find(id="methods")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
assert class_.find(id="package.Class.class_var")
assert class_.find(id="package.Class.method_okay")
def test_function(self, parse):
function_path = "_build/html/autoapi/package/function.html"
function_file = parse(function_path)
function_sig = function_file.find(id="package.function")
assert function_sig
function_path = "_build/html/autoapi/package/submodule/function.html"
function_file = parse(function_path)
assert function_file.find(id="package.submodule.function")
def test_rendered_only_expected_pages(self):
_, dirs, files = next(os.walk("_build/html/autoapi/package"))
assert sorted(dirs) == ["submodule", "subpackage"]
assert sorted(files) == [
"Class.NestedClass.html",
"Class.html",
"MyException.html",
"aliased_function.html",
"function.html",
"index.html",
]
_, dirs, files = next(os.walk("_build/html/autoapi/package/submodule"))
assert not dirs
assert sorted(files) == [
"Class.NestedClass.html",
"Class.html",
"MyException.html",
"aliased_function.html",
"function.html",
"index.html",
]
_, dirs, files = next(os.walk("_build/html/autoapi/package/subpackage"))
assert dirs == ["submodule"]
assert sorted(files) == ["aliased_function.html", "function.html", "index.html"]
_, dirs, files = next(
os.walk("_build/html/autoapi/package/subpackage/submodule")
)
assert not dirs
assert sorted(files) == [
"Class.NestedClass.html",
"Class.html",
"MyException.html",
"function.html",
"index.html",
"not_in_all_function.html",
]
def test_index(self, parse):
index_path = "_build/html/autoapi/index.html"
index_file = parse(index_path)
top_links = index_file.find_all(class_="toctree-l1")
top_hrefs = sorted(link.a["href"] for link in top_links)
assert top_hrefs == [
"#",
"package/index.html",
]
class TestMethod:
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={
"autoapi_own_page_level": "method",
"autoapi_options": [
"members",
"undoc-members",
"show-inheritance",
"imported-members",
],
},
)
def test_package(self, parse):
package_path = "_build/html/autoapi/package/index.html"
package_file = parse(package_path)
docstring = package_file.find("p")
assert docstring.text == "This is a docstring."
# There should be links to the children with their own page
subpackages = package_file.find(id="subpackages")
assert subpackages
assert subpackages.find("a", string="package.subpackage")
submodules = package_file.find(id="submodules")
assert submodules
assert submodules.find("a", string="package.submodule")
classes = package_file.find(id="classes")
assert classes
assert classes.find("a", title="package.Class")
exceptions = package_file.find(id="exceptions")
assert exceptions
assert exceptions.find("a", title="package.MyException")
functions = package_file.find(id="functions")
assert functions
assert functions.find("a", title="package.function")
# There should not be links to the children without their own page
assert not package_file.find(id="attributes")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
contents = package_file.find(id="package-contents")
assert contents.find(id="package.DATA")
assert not contents.find(id="package.MyException")
assert not contents.find(id="package.Class")
assert not contents.find(id="package.Class.class_var")
assert not contents.find(id="package.Class.NestedClass")
assert not contents.find(id="package.Class.method_okay")
assert not contents.find(id="package.Class.NestedClass")
assert not contents.find(id="package.Class.NestedClass.a_classmethod")
assert not contents.find(id="package.function")
assert not contents.find(id="package.aliased_function")
# Hidden children are never rendered.
assert not contents.find(id="package.not_in_all_function")
def test_module(self, parse):
submodule_path = "_build/html/autoapi/package/submodule/index.html"
submodule_file = parse(submodule_path)
docstring = submodule_file.find("p")
assert docstring.text == "Example module"
# There should be links to the children with their own page
exceptions = submodule_file.find(id="exceptions")
assert exceptions
assert exceptions.find("a", title="package.submodule.MyException")
classes = submodule_file.find(id="classes")
assert classes
assert classes.find("a", title="package.submodule.Class")
assert not classes.find("a", title="package.submodule.Class.NestedClass")
functions = submodule_file.find(id="functions")
assert functions
assert functions.find("a", title="package.submodule.function")
# There should not be links to the children without their own page
assert not submodule_file.find(id="submodules")
assert not submodule_file.find(id="subpackages")
assert not submodule_file.find(id="attributes")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
contents = submodule_file.find(id="module-contents")
assert contents.find(id="package.submodule.DATA")
assert not contents.find(id="package.submodule.MyException")
assert not contents.find(id="package.submodule.Class")
assert not contents.find(id="package.submodule.Class.class_var")
assert not contents.find(id="package.submodule.Class.NestedClass")
assert not contents.find(id="package.submodule.Class.method_okay")
assert not contents.find(id="package.submodule.Class.NestedClass")
assert not contents.find(id="package.submodule.Class.NestedClass.a_classmethod")
assert not contents.find(id="package.submodule.function")
assert not contents.find(id="package.submodule.aliased_function")
# Hidden children are never rendered.
assert not contents.find(id="package.submodule.not_in_all_function")
def test_class(self, parse):
class_path = "_build/html/autoapi/package/Class.html"
class_file = parse(class_path)
class_sig = class_file.find(id="package.Class")
assert class_sig
class_ = class_sig.parent
docstring = class_.find_all("p")[1]
assert docstring.text == "This is a class."
# There should be links to the children with their own page
classes = class_file.find(id="classes")
assert classes
assert classes.find("a", title="package.Class.NestedClass")
methods = class_file.find(id="methods")
assert methods
assert methods.find("a", title="package.Class.method_okay")
# There should not be links to the children without their own page
assert not class_file.find(id="attributes")
assert not class_file.find(id="exceptions")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
assert class_.find(id="package.Class.class_var")
assert not class_.find(id="package.Class.method_okay")
def test_function(self, parse):
function_path = "_build/html/autoapi/package/function.html"
function_file = parse(function_path)
function_sig = function_file.find(id="package.function")
assert function_sig
function_path = "_build/html/autoapi/package/submodule/function.html"
function_file = parse(function_path)
assert function_file.find(id="package.submodule.function")
def test_method(self, parse):
method_path = "_build/html/autoapi/package/Class.method_okay.html"
method_file = parse(method_path)
method_sig = method_file.find(id="package.Class.method_okay")
assert method_sig
method_path = "_build/html/autoapi/package/submodule/Class.method_okay.html"
method_file = parse(method_path)
assert method_file.find(id="package.submodule.Class.method_okay")
def test_rendered_only_expected_pages(self):
_, dirs, files = next(os.walk("_build/html/autoapi/package"))
assert sorted(dirs) == ["submodule", "subpackage"]
assert sorted(files) == [
"Class.NestedClass.a_classmethod.html",
"Class.NestedClass.html",
"Class.html",
"Class.method_okay.html",
"MyException.html",
"aliased_function.html",
"function.html",
"index.html",
]
_, dirs, files = next(os.walk("_build/html/autoapi/package/submodule"))
assert not dirs
assert sorted(files) == [
"Class.NestedClass.a_classmethod.html",
"Class.NestedClass.html",
"Class.html",
"Class.method_google_docs.html",
"Class.method_multiline.html",
"Class.method_okay.html",
"Class.method_sphinx_docs.html",
"Class.method_tricky.html",
"MyException.html",
"aliased_function.html",
"function.html",
"index.html",
]
_, dirs, files = next(os.walk("_build/html/autoapi/package/subpackage"))
assert dirs == ["submodule"]
assert sorted(files) == ["aliased_function.html", "function.html", "index.html"]
_, dirs, files = next(
os.walk("_build/html/autoapi/package/subpackage/submodule")
)
assert not dirs
assert sorted(files) == [
"Class.NestedClass.a_classmethod.html",
"Class.NestedClass.html",
"Class.html",
"Class.method_okay.html",
"MyException.html",
"function.html",
"index.html",
"not_in_all_function.html",
]
def test_index(self, parse):
index_path = "_build/html/autoapi/index.html"
index_file = parse(index_path)
top_links = index_file.find_all(class_="toctree-l1")
top_hrefs = sorted(link.a["href"] for link in top_links)
assert top_hrefs == [
"#",
"package/index.html",
]
class TestAttribute:
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={
"autoapi_own_page_level": "attribute",
"autoapi_options": [
"members",
"undoc-members",
"show-inheritance",
"imported-members",
],
},
)
# TODO: Include a test for a property
def test_package(self, parse):
package_path = "_build/html/autoapi/package/index.html"
package_file = parse(package_path)
docstring = package_file.find("p")
assert docstring.text == "This is a docstring."
# There should be links to the children with their own page
subpackages = package_file.find(id="subpackages")
assert subpackages
assert subpackages.find("a", string="package.subpackage")
submodules = package_file.find(id="submodules")
assert submodules
assert submodules.find("a", string="package.submodule")
classes = package_file.find(id="classes")
assert classes
assert classes.find("a", title="package.Class")
exceptions = package_file.find(id="exceptions")
assert exceptions
assert exceptions.find("a", title="package.MyException")
functions = package_file.find(id="functions")
assert functions
assert functions.find("a", title="package.function")
attributes = package_file.find(id="attributes")
assert attributes
assert attributes.find("a", title="package.DATA")
# There should not be links to the children without their own page
pass # there are no children without their own page
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
# Hidden children are never rendered.
assert not package_file.find(id="package-contents")
def test_module(self, parse):
submodule_path = "_build/html/autoapi/package/submodule/index.html"
submodule_file = parse(submodule_path)
docstring = submodule_file.find("p")
assert docstring.text == "Example module"
# There should be links to the children with their own page
exceptions = submodule_file.find(id="exceptions")
assert exceptions
assert exceptions.find("a", title="package.submodule.MyException")
classes = submodule_file.find(id="classes")
assert classes
assert classes.find("a", title="package.submodule.Class")
assert not classes.find("a", title="package.submodule.Class.NestedClass")
functions = submodule_file.find(id="functions")
assert functions
assert functions.find("a", title="package.submodule.function")
attributes = submodule_file.find(id="attributes")
assert attributes
assert attributes.find("a", title="package.submodule.DATA")
# There should not be links to the children without their own page
assert not submodule_file.find(id="submodules")
assert not submodule_file.find(id="subpackages")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
# Hidden children are never rendered.
assert not submodule_file.find(id="module-contents")
def test_class(self, parse):
class_path = "_build/html/autoapi/package/Class.html"
class_file = parse(class_path)
class_sig = class_file.find(id="package.Class")
assert class_sig
class_ = class_sig.parent
docstring = class_.find_all("p")[1]
assert docstring.text == "This is a class."
# There should be links to the children with their own page
classes = class_file.find(id="classes")
assert classes
assert classes.find("a", title="package.Class.NestedClass")
methods = class_file.find(id="methods")
assert methods
assert methods.find("a", title="package.Class.method_okay")
attributes = class_file.find(id="attributes")
assert attributes
assert attributes.find("a", title="package.Class.class_var")
# There should not be links to the children without their own page
assert not class_file.find(id="exceptions")
# Children without their own page should be rendered on this page,
# and children with their own page should not be rendered on this page.
assert not class_.find(id="package.Class.class_var")
assert not class_.find(id="package.Class.method_okay")
def test_function(self, parse):
function_path = "_build/html/autoapi/package/function.html"
function_file = parse(function_path)
function_sig = function_file.find(id="package.function")
assert function_sig
function_path = "_build/html/autoapi/package/submodule/function.html"
function_file = parse(function_path)
assert function_file.find(id="package.submodule.function")
def test_method(self, parse):
method_path = "_build/html/autoapi/package/Class.method_okay.html"
method_file = parse(method_path)
method_sig = method_file.find(id="package.Class.method_okay")
assert method_sig
method_path = "_build/html/autoapi/package/submodule/Class.method_okay.html"
method_file = parse(method_path)
assert method_file.find(id="package.submodule.Class.method_okay")
def test_data(self, parse):
data_path = "_build/html/autoapi/package/DATA.html"
data_file = parse(data_path)
data_sig = data_file.find(id="package.DATA")
assert data_sig
def test_attribute(self, parse):
attribute_path = "_build/html/autoapi/package/Class.class_var.html"
attribute_file = parse(attribute_path)
attribute_sig = attribute_file.find(id="package.Class.class_var")
assert attribute_sig
def test_rendered_only_expected_pages(self):
_, dirs, files = next(os.walk("_build/html/autoapi/package"))
assert sorted(dirs) == ["submodule", "subpackage"]
assert sorted(files) == [
"Class.NestedClass.a_classmethod.html",
"Class.NestedClass.html",
"Class.class_var.html",
"Class.html",
"Class.method_okay.html",
"DATA.html",
"MyException.html",
"aliased_function.html",
"function.html",
"index.html",
]
_, dirs, files = next(os.walk("_build/html/autoapi/package/submodule"))
assert not dirs
assert sorted(files) == [
"Class.NestedClass.a_classmethod.html",
"Class.NestedClass.html",
"Class.class_var.html",
"Class.html",
"Class.method_google_docs.html",
"Class.method_multiline.html",
"Class.method_okay.html",
"Class.method_sphinx_docs.html",
"Class.method_tricky.html",
"DATA.html",
"MyException.html",
"aliased_function.html",
"function.html",
"index.html",
]
_, dirs, files = next(os.walk("_build/html/autoapi/package/subpackage"))
assert dirs == ["submodule"]
assert sorted(files) == ["aliased_function.html", "function.html", "index.html"]
_, dirs, files = next(
os.walk("_build/html/autoapi/package/subpackage/submodule")
)
assert not dirs
assert sorted(files) == [
"Class.NestedClass.a_classmethod.html",
"Class.NestedClass.html",
"Class.class_var.html",
"Class.html",
"Class.method_okay.html",
"DATA.html",
"MyException.html",
"function.html",
"index.html",
"not_in_all_function.html",
]
def test_index(self, parse):
index_path = "_build/html/autoapi/index.html"
index_file = parse(index_path)
top_links = index_file.find_all(class_="toctree-l1")
top_hrefs = sorted(link.a["href"] for link in top_links)
assert top_hrefs == [
"#",
"package/index.html",
]
@pytest.mark.parametrize(
"value", ["package", "exception", "property", "data", "not_a_value"]
)
def test_invalid_values(builder, value):
"""Test failure when autoapi_own_page_level is invalid."""
with pytest.raises(ValueError):
builder(
"pypackageexample",
confoverrides={
"autoapi_own_page_level": value,
},
)

@ -1,7 +1,6 @@
import io
import os
import pathlib
import shutil
import sys
from unittest.mock import Mock, call
@ -13,7 +12,6 @@ from autoapi.mappers.python import (
PythonMethod,
PythonModule,
)
from bs4 import BeautifulSoup
from packaging import version
import pytest
import sphinx
@ -24,60 +22,6 @@ import sphinx.util.logging
sphinx_version = version.parse(sphinx.__version__).release
def rebuild(confdir=".", **kwargs):
app = Sphinx(
srcdir=".",
confdir=confdir,
outdir="_build/html",
doctreedir="_build/.doctrees",
buildername="html",
**kwargs,
)
app.build()
@pytest.fixture(scope="class")
def builder():
cwd = os.getcwd()
def build(test_dir, **kwargs):
if kwargs.get("warningiserror"):
# Add any warnings raised when using `Sphinx` more than once
# in a Python session.
confoverrides = kwargs.setdefault("confoverrides", {})
confoverrides.setdefault("suppress_warnings", [])
suppress = confoverrides["suppress_warnings"]
suppress.append("app.add_node")
suppress.append("app.add_directive")
suppress.append("app.add_role")
os.chdir("tests/python/{0}".format(test_dir))
rebuild(**kwargs)
yield build
try:
shutil.rmtree("_build")
if (pathlib.Path("autoapi") / "index.rst").exists():
shutil.rmtree("autoapi")
finally:
os.chdir(cwd)
@pytest.fixture(scope="class")
def parse():
cache = {}
def parser(path):
if path not in cache:
with io.open(path, encoding="utf8") as file_handle:
cache[path] = BeautifulSoup(file_handle, features="html.parser")
return cache[path]
yield parser
class TestSimpleModule:
@pytest.fixture(autouse=True, scope="class")
def built(self, builder):
@ -200,7 +144,7 @@ class TestSimpleModule:
def test_long_signature(self, parse):
example_file = parse("_build/html/autoapi/example/index.html")
summary_row = example_file.find_all(class_="autosummary")[1].find_all("tr")[-1]
summary_row = example_file.find_all(class_="autosummary")[-1].find_all("tr")[-1]
assert summary_row
cells = summary_row.find_all("td")
assert (
@ -670,26 +614,26 @@ class TestSimplePackage:
builder("pypackageexample", warningiserror=True)
def test_integration_with_package(self, parse):
example_file = parse("_build/html/autoapi/example/index.html")
example_file = parse("_build/html/autoapi/package/index.html")
entries = example_file.find_all(class_="toctree-l1")
assert any(entry.text == "example.foo" for entry in entries)
assert example_file.find(id="example.module_level_function")
assert any(entry.text == "package.submodule" for entry in entries)
assert example_file.find(id="package.function")
example_foo_file = parse("_build/html/autoapi/example/foo/index.html")
example_foo_file = parse("_build/html/autoapi/package/submodule/index.html")
foo = example_foo_file.find(id="example.foo.Foo")
assert foo
method_okay = foo.parent.find(id="example.foo.Foo.method_okay")
submodule = example_foo_file.find(id="package.submodule.Class")
assert submodule
method_okay = submodule.parent.find(id="package.submodule.Class.method_okay")
assert method_okay
index_file = parse("_build/html/index.html")
toctree = index_file.select("li > a")
assert any(item.text == "API Reference" for item in toctree)
assert any(item.text == "example.foo" for item in toctree)
assert any(item.text == "Foo" for item in toctree)
assert any(item.text == "module_level_function()" for item in toctree)
assert any(item.text == "package.submodule" for item in toctree)
assert any(item.text == "Class" for item in toctree)
assert any(item.text == "function()" for item in toctree)
def test_simple_no_false_warnings(builder, caplog):
@ -738,14 +682,14 @@ def test_hiding_private_members(builder, parse):
confoverrides = {"autoapi_options": ["members", "undoc-members", "special-members"]}
builder("pypackageexample", warningiserror=True, confoverrides=confoverrides)
example_file = parse("_build/html/autoapi/example/index.html")
example_file = parse("_build/html/autoapi/package/index.html")
entries = example_file.find_all(class_="toctree-l1")
assert all("private" not in entry.text for entry in entries)
private_file = parse("_build/html/autoapi/example/_private_module/index.html")
assert private_file.find(id="example._private_module.PrivateClass.public_method")
assert not pathlib.Path(
"_build/html/autoapi/package/_private_module/index.html"
).exists()
def test_hiding_inheritance(builder, parse):
@ -1019,7 +963,7 @@ class TestComplexPackageParallel(TestComplexPackage):
builder("pypackagecomplex", parallel=2)
def test_caching(builder):
def test_caching(builder, rebuild):
mtimes = (0, 0)
def record_mtime():
@ -1125,25 +1069,25 @@ def test_string_module_attributes(builder):
".. py:data:: code_snippet",
" :value: Multiline-String",
"",
" .. raw:: html",
" .. raw:: html",
"",
" <details><summary>Show Value</summary>",
" <details><summary>Show Value</summary>",
"",
" .. code-block:: python",
" .. code-block:: python",
"",
' """The following is some code:',
" ", # <--- Line array monstrosity to preserve these leading spaces
" # -*- coding: utf-8 -*-",
" from __future__ import absolute_import, division, print_function, unicode_literals",
" # from future.builtins.disabled import *",
" # from builtins import *",
" ",
""" print("chunky o'block")""",
' """',
' """The following is some code:',
" ", # <--- Line array monstrosity to preserve these leading spaces
" # -*- coding: utf-8 -*-",
" from __future__ import absolute_import, division, print_function, unicode_literals",
" # from future.builtins.disabled import *",
" # from builtins import *",
" ",
""" print("chunky o'block")""",
' """',
"",
" .. raw:: html",
" .. raw:: html",
"",
" </details>",
" </details>",
]
assert "\n".join(code_snippet_contents) in example_file
@ -1208,298 +1152,3 @@ class TestMemberOrder:
method_sphinx_docs = example_file.find(id="example.Foo.method_sphinx_docs")
assert method_tricky.sourceline < method_sphinx_docs.sourceline
# TODO: This might be easier to understand with its own test case.
# Eg make a package named "package", subpackage named "subpackage",
# submodule named "submodule", etc.
class TestOwnPageLevel:
def test_package(self, builder, parse):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={"autoapi_own_page_level": "package"},
)
example_path = "_build/html/autoapi/example/index.html"
example_file = parse(example_path)
# TODO: Look for expected contents
# subpackage_path = "_build/html/autoapi/example/subpackage/index.html"
# subpackage_file = parse(subpackage_path)
# TODO: Look for expected contents
assert not os.path.exists("_build/html/autoapi/example/foo/index.html")
assert not os.path.exists("_build/html/autoapi/example/foo/FooError.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo.html")
assert not os.path.exists("_build/html/autoapi/example/foo/module_level_function.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/method_okay.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/property.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/class_var.html")
assert not os.path.exists("_build/html/autoapi/example/foo/MODULE_DATA.html")
def test_module(self, builder, parse):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={"autoapi_own_page_level": "module"},
)
example_path = "_build/html/autoapi/example/index.html"
example_file = parse(example_path)
# TODO: Look for expected contents
#subpackage_path = "_build/html/autoapi/example/subpackage/index.html"
#subpackage_file = parse(subpackage_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/index.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
assert not os.path.exists("_build/html/autoapi/example/foo/FooError.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo.html")
assert not os.path.exists("_build/html/autoapi/example/foo/module_level_function.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/method_okay.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/property.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/class_var.html")
assert not os.path.exists("_build/html/autoapi/example/foo/MODULE_DATA.html")
def test_class(self, builder, parse):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={"autoapi_own_page_level": "class"},
)
example_path = "_build/html/autoapi/example/index.html"
example_file = parse(example_path)
# TODO: Look for expected contents
#subpackage_path = "_build/html/autoapi/example/subpackage/index.html"
#subpackage_file = parse(subpackage_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/index.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
#error_path = "_build/html/autoapi/example/foo/FooError.html"
#error_file = parse(error_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/Foo.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
assert not os.path.exists("_build/html/autoapi/example/foo/module_level_function.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/method_okay.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/property.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/class_var.html")
assert not os.path.exists("_build/html/autoapi/example/foo/MODULE_DATA.html")
def test_function(self, builder, parse):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={"autoapi_own_page_level": "function"},
)
example_path = "_build/html/autoapi/example/index.html"
example_file = parse(example_path)
# TODO: Look for expected contents
# subpackage_path = "_build/html/autoapi/example/subpackage/index.html"
# subpackage_file = parse(subpackage_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/index.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
# error_path = "_build/html/autoapi/example/foo/FooError.html"
# error_file = parse(error_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/Foo/index.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
func_path = "_build/html/autoapi/example/module_level_function.html"
func_file = parse(func_path)
# TODO: Look for expected contents
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/method_okay.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/property.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/class_var.html")
assert not os.path.exists("_build/html/autoapi/example/foo/MODULE_DATA.html")
def test_method(self, builder, parse):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={"autoapi_own_page_level": "method"},
)
example_path = "_build/html/autoapi/example/index.html"
example_file = parse(example_path)
# TODO: Look for expected contents
# subpackage_path = "_build/html/autoapi/example/subpackage/index.html"
# subpackage_file = parse(subpackage_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/index.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
error_path = "_build/html/autoapi/example/foo/FooError.html"
error_file = parse(error_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/Foo.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
func_path = "_build/html/autoapi/example/foo/module_level_function.html"
func_file = parse(func_path)
# TODO: Look for expected contents
method_path = "_build/html/autoapi/example/foo/Foo/method_okay.html"
method_file = parse(method_path)
# TODO: Look for expected contents
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/property.html")
assert not os.path.exists("_build/html/autoapi/example/foo/Foo/class_var.html")
assert not os.path.exists("_build/html/autoapi/example/foo/MODULE_DATA.html")
def test_attribute(self, builder, parse):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={"autoapi_own_page_level": "attribute"},
)
example_path = "_build/html/autoapi/example/index.html"
example_file = parse(example_path)
# TODO: Look for expected contents
# subpackage_path = "_build/html/autoapi/example/subpackage/index.html"
# subpackage_file = parse(subpackage_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/index.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
error_path = "_build/html/autoapi/example/foo/FooError.html"
error_file = parse(error_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/Foo.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
func_path = "_build/html/autoapi/example/foo/module_level_function.html"
func_file = parse(func_path)
# TODO: Look for expected contents
method_path = "_build/html/autoapi/example/foo/Foo/method_okay.html"
method_file = parse(method_path)
# TODO: Look for expected contents
property_path = "_build/html/autoapi/example/foo/Foo/property.html"
property_file = parse(property_path)
# TODO: Look for expected contents
attribute_path = "_build/html/autoapi/example/foo/Foo/class_var.html"
attribute_file = parse(attribute_path)
# TODO: Look for expected contents
assert not os.path.exists("_build/html/autoapi/example/foo/MODULE_DATA.html")
def test_data(self, builder, parse):
builder(
"pypackageexample",
warningiserror=True,
confoverrides={"autoapi_own_page_level": "data"},
)
example_path = "_build/html/autoapi/example/index.html"
example_file = parse(example_path)
# TODO: Look for expected contents
# subpackage_path = "_build/html/autoapi/example/subpackage/index.html"
# subpackage_file = parse(subpackage_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/index.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
error_path = "_build/html/autoapi/example/foo/FooError.html"
error_file = parse(error_path)
# TODO: Look for expected contents
foo_path = "_build/html/autoapi/example/foo/Foo.html"
foo_file = parse(foo_path)
# TODO: Look for expected contents
func_path = "_build/html/autoapi/example/foo/module_level_function.html"
func_file = parse(func_path)
# TODO: Look for expected contents
method_path = "_build/html/autoapi/example/foo/Foo/method_okay.html"
method_file = parse(method_path)
# TODO: Look for expected contents
property_path = "_build/html/autoapi/example/foo/Foo/property.html"
property_file = parse(property_path)
# TODO: Look for expected contents
attribute_path = "_build/html/autoapi/example/foo/Foo/class_var.html"
attribute_file = parse(attribute_path)
# TODO: Look for expected contents
data_path = "_build/html/autoapi/example/foo/MODULE_DATA.html"
data_file = parse(data_path)
# TODO: Look for expected contents

Loading…
Cancel
Save