Skip to content

Commit 86382d9

Browse files
committed
Add and use HideReason enum
1 parent 3ac44d8 commit 86382d9

File tree

2 files changed

+136
-90
lines changed

2 files changed

+136
-90
lines changed

autoapi/_mapper.py

Lines changed: 49 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
PythonData,
3131
PythonException,
3232
_trace_visibility,
33+
HideReason,
3334
)
3435
from .settings import OWN_PAGE_LEVELS, TEMPLATE_DIR
3536

@@ -45,12 +46,7 @@ def in_stdlib(module_name: str) -> bool:
4546

4647

4748
def _color_info(msg: str) -> None:
48-
LOGGER.info(
49-
colorize("bold", "[AutoAPI] ")
50-
+ colorize(
51-
"darkgreen", msg
52-
)
53-
)
49+
LOGGER.info(colorize("bold", "[AutoAPI] ") + colorize("darkgreen", msg))
5450

5551

5652
def _expand_wildcard_placeholder(original_module, originals_map, placeholder):
@@ -389,13 +385,6 @@ def _output_top_rst(self):
389385
# Render Top Index
390386
top_level_index = os.path.join(self.dir_root, "index.rst")
391387

392-
modules = [obj for obj in self.all_objects.values()
393-
if obj.type == "module" and obj.docstring == ""]
394-
if modules and "undoc-members" not in self.app.config.autoapi_options:
395-
_color_info("The following modules have no top-level documentation, and so were skipped as undocumented:")
396-
for m in modules:
397-
_color_info(f" {m.id}")
398-
399388
pages = [obj for obj in self.objects_to_render.values() if obj.display]
400389
if not pages:
401390
msg = (
@@ -507,8 +496,12 @@ def _skip_if_stdlib(self):
507496
and not obj["inherited_from"]["is_abstract"]
508497
and module not in documented_modules
509498
):
510-
_trace_visibility(self.app, f"Hiding {obj['qual_name']} as determined to be Python standard Library (found as {obj['full_name']})", verbose=2)
511-
obj["hide"] = True
499+
_trace_visibility(
500+
self.app,
501+
f"Hiding {obj['qual_name']} as determined to be Python standard Library (found as {obj['full_name']})",
502+
verbose=2,
503+
)
504+
obj["hide_reason"] = HideReason.STD_LIBRARY
512505

513506
def _resolve_placeholders(self):
514507
"""Resolve objects that have been imported from elsewhere."""
@@ -527,19 +520,27 @@ def _hide_yo_kids(self):
527520
for module in self.paths.values():
528521
if module["all"] is not None:
529522
all_names = set(module["all"])
530-
_trace_visibility(self.app, f"{module['full_name']}: Found __all__ =")
531523
for n in all_names:
532524
_trace_visibility(self.app, f" {n}")
533525
for child in module["children"]:
534526
if child["qual_name"] not in all_names:
535-
_trace_visibility(self.app, f"Hiding {child['full_name']}, as {child['qual_name']} not in __all__")
536-
child["hide"] = True
527+
_trace_visibility(
528+
self.app,
529+
f"Hiding {child['full_name']}, as {child['qual_name']} not in __all__",
530+
)
531+
child["hide_reason"] = HideReason.NOT_IN_ALL
537532
elif module["type"] == "module":
538-
_trace_visibility(self.app, f"Testing if any children of {module['full_name']} have already been documented")
533+
_trace_visibility(
534+
self.app,
535+
f"Testing if any children of {module['full_name']} have already been documented",
536+
)
539537
for child in module["children"]:
540538
if "original_path" in child:
541-
_trace_visibility(self.app, f"Hiding {child['full_name']} as documented at {child['original_path']}")
542-
child["hide"] = True
539+
_trace_visibility(
540+
self.app,
541+
f"Hiding {child['full_name']} as it appears to be in your public API at {child['original_path']}",
542+
)
543+
child["hide_reason"] = HideReason.NOT_PUBLIC
543544

544545
def map(self, options=None):
545546
self._skip_if_stdlib()
@@ -583,17 +584,27 @@ def _render_selection(self):
583584
self.objects_to_render[obj.id] = obj
584585
else:
585586
if obj.subpackages or obj.submodules:
586-
_trace_visibility(self.app, f"Not rendering the following as {obj.id} set to not display:", verbose=2)
587+
_trace_visibility(
588+
self.app,
589+
f"Not rendering the following as {obj.id} set to not display because object is {obj.hide_reason}",
590+
verbose=2,
591+
)
587592
for module in itertools.chain(obj.subpackages, obj.submodules):
588-
_trace_visibility(self.app, f" {module.obj['full_name']}", verbose=2)
589-
module.obj["hide"] = True
593+
_trace_visibility(
594+
self.app, f" {module.obj['full_name']}", verbose=2
595+
)
596+
module.obj["hide_reason"] = HideReason.PARENT_HIDDEN
590597

591598
def _inner(parent):
592599
for child in parent.children:
593600
self.all_objects[child.id] = child
594601
if not parent.display:
595-
_trace_visibility(self.app, f"Hiding {child.id} as parent {parent.id} will not be displayed", verbose=2)
596-
child.obj["hide"] = True
602+
_trace_visibility(
603+
self.app,
604+
f"Hiding {child.id} as parent {parent.id} will not be displayed",
605+
verbose=2,
606+
)
607+
child.obj["hide_reason"] = HideReason.PARENT_HIDDEN
597608

598609
if child.display and child.type in self.own_page_types:
599610
self.objects_to_render[child.id] = child
@@ -603,6 +614,18 @@ def _inner(parent):
603614
for obj in list(self.all_objects.values()):
604615
_inner(obj)
605616

617+
modules = [
618+
obj
619+
for obj in self.all_objects.values()
620+
if obj.type == "module" and obj.docstring == ""
621+
]
622+
if modules and "undoc-members" not in self.app.config.autoapi_options:
623+
_color_info(
624+
"The following modules have no top-level documentation, and so were skipped as undocumented:"
625+
)
626+
for m in modules:
627+
_color_info(f" {m.id}")
628+
606629
def create_class(self, data, options=None):
607630
"""Create a class from the passed in data
608631

autoapi/_objects.py

Lines changed: 87 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,13 @@
22

33
import functools
44
import pathlib
5+
import sys
6+
7+
if sys.version_info >= (3, 11):
8+
from enum import StrEnum
9+
else:
10+
from backports.strenum import StrEnum
11+
from typing import List
512

613
import sphinx
714
import sphinx.util
@@ -13,6 +20,7 @@
1320

1421
LOGGER = sphinx.util.logging.getLogger(__name__)
1522

23+
1624
def _trace_visibility(app, msg: str, verbose=1) -> None:
1725
if app.config.autoapi_verbose_visibility >= verbose:
1826
LOGGER.info(colorize("bold", f"[AutoAPI] [Visibility] {msg}"))
@@ -35,6 +43,21 @@ def _format_args(args_info, include_annotations=True, ignore_self=None):
3543
return ", ".join(result)
3644

3745

46+
class HideReason(StrEnum):
47+
NOT_HIDDEN = "not hidden"
48+
UNDOC_MEMBER = "undocumented"
49+
PRIVATE_MEMBER = "a private member"
50+
SPECIAL_MEMBER = "a special member"
51+
IMPORTED_MEMBER = "an imported member"
52+
INHERITED_MEMBER = "an inherited member"
53+
IS_NEW_OR_INIT = "__new__ or __init__"
54+
NOT_IN_ALL = "not in __all__ for module"
55+
NOT_PUBLIC = "assumed to not be public API"
56+
PARENT_HIDDEN = "parent is hidden"
57+
STD_LIBRARY = "part of Python standard library"
58+
SKIP_MEMBER = "`autoapi-skip-member` returned false for the object"
59+
60+
3861
class PythonObject:
3962
"""A class representing an entity from the parsed source code.
4063
@@ -50,7 +73,13 @@ class PythonObject:
5073
type: str
5174

5275
def __init__(
53-
self, obj, jinja_env, app, url_root, options=None, class_content="class"
76+
self,
77+
obj,
78+
jinja_env,
79+
app,
80+
url_root,
81+
options: List[str] = [],
82+
class_content="class",
5483
):
5584
self.app = app
5685
self.obj = obj
@@ -84,8 +113,7 @@ def __init__(
84113

85114
# For later
86115
self._class_content = class_content
87-
self._display_cache: bool | None = None
88-
self._skip_reason = None
116+
self._display_cache: HideReason | None = None
89117

90118
def __getstate__(self):
91119
"""Obtains serialisable data for pickling."""
@@ -199,22 +227,65 @@ def is_special_member(self) -> bool:
199227
"""Whether this object is a special member (True) or not (False)."""
200228
return self.short_name.startswith("__") and self.short_name.endswith("__")
201229

230+
@property
231+
def hide_reason(self) -> HideReason:
232+
skip_undoc_member = self.is_undoc_member and "undoc-members" not in self.options
233+
skip_private_member = (
234+
self.is_private_member and "private-members" not in self.options
235+
)
236+
skip_special_member = (
237+
self.is_special_member and "special-members" not in self.options
238+
)
239+
skip_imported_member = self.imported and "imported-members" not in self.options
240+
skip_inherited_member = (
241+
self.inherited and "inherited-members" not in self.options
242+
)
243+
244+
reason = HideReason.NOT_HIDDEN
245+
if self.obj.get("hide-reason"):
246+
reason = self.obj.get("hide-reason")
247+
elif skip_undoc_member:
248+
reason = HideReason.UNDOC_MEMBER
249+
elif skip_private_member:
250+
reason = HideReason.UNDOC_MEMBER
251+
elif skip_special_member:
252+
reason = HideReason.SPECIAL_MEMBER
253+
elif skip_imported_member:
254+
reason = HideReason.IMPORTED_MEMBER
255+
elif skip_inherited_member:
256+
reason = HideReason.INHERITED_MEMBER
257+
258+
# Allow user to override
259+
# If we told the api we were skipping already, keep the reason as originally
260+
skip = reason != HideReason.NOT_HIDDEN
261+
api_says_skip = self.app.emit_firstresult(
262+
"autoapi-skip-member", self.type, self.id, self, skip, self.options
263+
)
264+
if not skip and api_says_skip:
265+
reason = HideReason.SKIP_MEMBER
266+
267+
return reason
268+
202269
@property
203270
def display(self) -> bool:
204271
"""Whether this object should be displayed in documentation.
205272
206273
This attribute depends on the configuration options given in
207274
:confval:`autoapi_options` and the result of :event:`autoapi-skip-member`.
208275
"""
209-
skip = self._should_skip()
276+
210277
if self._display_cache is None:
211-
self._display_cache = not self._ask_ignore(skip)
212-
if self._display_cache is False:
213-
_trace_visibility(self.app, self._skip_reason)
278+
self._display_cache = self.hide_reason
279+
if self._display_cache != HideReason.NOT_HIDDEN:
280+
_trace_visibility(
281+
self.app, f"Skipping {self.id} due to {self.hide_reason}"
282+
)
214283
else:
215-
_trace_visibility(self.app, f"Skipping {self.id} due to cache", verbose=2)
284+
_trace_visibility(
285+
self.app, f"Skipping {self.id} due to {self.hide_reason}", verbose=2
286+
)
216287

217-
return self._display_cache
288+
return self._display_cache == HideReason.NOT_HIDDEN
218289

219290
@property
220291
def summary(self) -> str:
@@ -230,57 +301,6 @@ def summary(self) -> str:
230301

231302
return ""
232303

233-
def _should_skip(self) -> bool:
234-
skip_undoc_member = self.is_undoc_member and "undoc-members" not in self.options
235-
skip_private_member = (
236-
self.is_private_member and "private-members" not in self.options
237-
)
238-
skip_special_member = (
239-
self.is_special_member and "special-members" not in self.options
240-
)
241-
skip_imported_member = self.imported and "imported-members" not in self.options
242-
skip_inherited_member = (
243-
self.inherited and "inherited-members" not in self.options
244-
)
245-
246-
reason = ""
247-
if self.obj.get("hide", False):
248-
reason = "marked hidden by mapper"
249-
elif skip_undoc_member:
250-
reason = "is undocumented"
251-
elif skip_private_member:
252-
reason = "is a private member"
253-
elif skip_special_member:
254-
reason = "is a special member"
255-
elif skip_imported_member:
256-
reason = "is an imported member"
257-
elif skip_inherited_member:
258-
reason = "is an inherited member"
259-
260-
self._skip_reason = f"Skipping {self.id} as {reason}"
261-
262-
return (
263-
self.obj.get("hide", False)
264-
or skip_undoc_member
265-
or skip_private_member
266-
or skip_special_member
267-
or skip_imported_member
268-
or skip_inherited_member
269-
)
270-
271-
def _ask_ignore(self, skip: bool) -> bool:
272-
273-
ask_result = self.app.emit_firstresult(
274-
"autoapi-skip-member", self.type, self.id, self, skip, self.options
275-
)
276-
277-
if ask_result is not None:
278-
reason = f"Skipping as 'autoapi-skip-member' returned {ask_result}"
279-
if self._skip_reason:
280-
reason += f"Passed skip={skip} to 'autoapi-skip-member' as {self._skip_reason}"
281-
self._skip_reason = reason
282-
return ask_result if ask_result is not None else skip
283-
284304
def _children_of_type(self, type_: str) -> list[PythonObject]:
285305
return [child for child in self.children if child.type == type_]
286306

@@ -339,14 +359,17 @@ def __init__(self, *args, **kwargs):
339359
Can be any of: abstractmethod, async, classmethod, property, staticmethod.
340360
"""
341361

342-
def _should_skip(self) -> bool:
362+
@property
363+
def hide_reason(self) -> HideReason:
343364
is_new_or_init = self.name in (
344365
"__new__",
345366
"__init__",
346367
)
347-
if not super()._should_skip and is_new_or_init:
348-
self._skip_reason = f"Skipping method {self.id} as is __new__ or __init__"
349-
return super()._should_skip() or is_new_or_init
368+
hide_reason = super().hide_reason
369+
if hide_reason != HideReason.NOT_HIDDEN and is_new_or_init:
370+
return HideReason.IS_NEW_OR_INIT
371+
return hide_reason
372+
350373

351374
class PythonProperty(PythonObject):
352375
"""The representation of a property on a class."""

0 commit comments

Comments
 (0)