Skip to content

Commit

Permalink
Enable automatic formatting for sphinx/ext/autodoc/
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner committed Dec 14, 2024
1 parent f4b397f commit e81c4e8
Show file tree
Hide file tree
Showing 8 changed files with 852 additions and 435 deletions.
7 changes: 0 additions & 7 deletions .ruff.toml
Original file line number Diff line number Diff line change
Expand Up @@ -415,13 +415,6 @@ exclude = [
"sphinx/domains/python/_object.py",
"sphinx/domains/rst.py",
"sphinx/domains/std/__init__.py",
"sphinx/ext/autodoc/__init__.py",
"sphinx/ext/autodoc/directive.py",
"sphinx/ext/autodoc/importer.py",
"sphinx/ext/autodoc/mock.py",
"sphinx/ext/autodoc/preserve_defaults.py",
"sphinx/ext/autodoc/type_comment.py",
"sphinx/ext/autodoc/typehints.py",
"sphinx/ext/autosectionlabel.py",
"sphinx/ext/autosummary/__init__.py",
"sphinx/ext/coverage.py",
Expand Down
929 changes: 608 additions & 321 deletions sphinx/ext/autodoc/__init__.py

Large diffs are not rendered by default.

64 changes: 47 additions & 17 deletions sphinx/ext/autodoc/directive.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,27 @@


# common option names for autodoc directives
AUTODOC_DEFAULT_OPTIONS = ['members', 'undoc-members', 'inherited-members',
'show-inheritance', 'private-members', 'special-members',
'ignore-module-all', 'exclude-members', 'member-order',
'imported-members', 'class-doc-from', 'no-value']

AUTODOC_EXTENDABLE_OPTIONS = ['members', 'private-members', 'special-members',
'exclude-members']
AUTODOC_DEFAULT_OPTIONS = [
'members',
'undoc-members',
'inherited-members',
'show-inheritance',
'private-members',
'special-members',
'ignore-module-all',
'exclude-members',
'member-order',
'imported-members',
'class-doc-from',
'no-value',
]

AUTODOC_EXTENDABLE_OPTIONS = [
'members',
'private-members',
'special-members',
'exclude-members',
]


class DummyOptionSpec(dict[str, Callable[[str], str]]):
Expand All @@ -46,8 +60,14 @@ def __getitem__(self, _key: str) -> Callable[[str], str]:
class DocumenterBridge:
"""A parameters container for Documenters."""

def __init__(self, env: BuildEnvironment, reporter: Reporter | None, options: Options,
lineno: int, state: Any) -> None:
def __init__(
self,
env: BuildEnvironment,
reporter: Reporter | None,
options: Options,
lineno: int,
state: Any,
) -> None:
self.env = env
self._reporter = reporter
self.genopt = options
Expand All @@ -58,7 +78,7 @@ def __init__(self, env: BuildEnvironment, reporter: Reporter | None, options: Op


def process_documenter_options(
documenter: type[Documenter], config: Config, options: dict[str, str],
documenter: type[Documenter], config: Config, options: dict[str, str]
) -> Options:
"""Recognize options of Documenter from user input."""
default_options = config.autodoc_default_options
Expand All @@ -83,8 +103,9 @@ def process_documenter_options(
return Options(assemble_option_dict(options.items(), documenter.option_spec))


def parse_generated_content(state: RSTState, content: StringList, documenter: Documenter,
) -> list[Node]:
def parse_generated_content(
state: RSTState, content: StringList, documenter: Documenter
) -> list[Node]:
"""Parse an item of content generated by Documenter."""
with switch_source_input(state, content):
if documenter.titles_allowed:
Expand Down Expand Up @@ -115,7 +136,8 @@ def run(self) -> list[Node]:

try:
source, lineno = reporter.get_source_and_line( # type: ignore[attr-defined]
self.lineno)
self.lineno
)
except AttributeError:
source, lineno = (None, None)
logger.debug('[autodoc] %s:%s: input:\n%s', source, lineno, self.block_text)
Expand All @@ -126,15 +148,23 @@ def run(self) -> list[Node]:

# process the options with the selected documenter's option_spec
try:
documenter_options = process_documenter_options(doccls, self.config, self.options)
documenter_options = process_documenter_options(
doccls, self.config, self.options
)
except (KeyError, ValueError, TypeError) as exc:
# an option is either unknown or has a wrong type
logger.error('An option to %s is either unknown or has an invalid value: %s',
self.name, exc, location=(self.env.docname, lineno))
logger.error(
'An option to %s is either unknown or has an invalid value: %s',
self.name,
exc,
location=(self.env.docname, lineno),
)
return []

# generate the output
params = DocumenterBridge(self.env, reporter, documenter_options, lineno, self.state)
params = DocumenterBridge(
self.env, reporter, documenter_options, lineno, self.state
)
documenter = doccls(params, self.arguments[0])
documenter.generate(more_content=self.content)
if not params.result:
Expand Down
123 changes: 88 additions & 35 deletions sphinx/ext/autodoc/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,13 @@ def _filter_enum_dict(
candidate_in_mro: set[str] = set()
# sunder names that were picked up (and thereby allowed to be redefined)
# see: https://docs.python.org/3/howto/enum.html#supported-dunder-names
sunder_names = {'_name_', '_value_', '_missing_', '_order_', '_generate_next_value_'}
sunder_names = {
'_name_',
'_value_',
'_missing_',
'_order_',
'_generate_next_value_',
}
# attributes that can be picked up on a mixin type or the enum's data type
public_names = {'name', 'value', *object.__dict__, *sunder_names}
# names that are ignored by default
Expand Down Expand Up @@ -88,8 +94,14 @@ def query(name: str, defining_class: type) -> tuple[str, type, Any] | None:
# exclude members coming from the native Enum unless
# they were redefined on a mixin type or the data type
excluded_members = Enum.__dict__.keys() - candidate_in_mro
yield from filter(None, (query(name, enum_class) for name in enum_class_dict
if name not in excluded_members))
yield from filter(
None,
(
query(name, enum_class)
for name in enum_class_dict
if name not in excluded_members
),
)

# check if allowed members from ``Enum`` were redefined at the enum level
special_names = sunder_names | public_names
Expand All @@ -112,7 +124,7 @@ def mangle(subject: Any, name: str) -> str:
"""Mangle the given name."""
try:
if isclass(subject) and name.startswith('__') and not name.endswith('__'):
return f"_{subject.__name__}{name}"
return f'_{subject.__name__}{name}'
except AttributeError:
pass

Expand All @@ -123,12 +135,12 @@ def unmangle(subject: Any, name: str) -> str | None:
"""Unmangle the given name."""
try:
if isclass(subject) and not name.endswith('__'):
prefix = "_%s__" % subject.__name__
prefix = f'_{subject.__name__}__'
if name.startswith(prefix):
return name.replace(prefix, "__", 1)
return name.replace(prefix, '__', 1)
else:
for cls in subject.__mro__:
prefix = "_%s__" % cls.__name__
prefix = f'_{cls.__name__}__'
if name.startswith(prefix):
# mangled attribute defined in parent class
return None
Expand Down Expand Up @@ -160,8 +172,12 @@ def _reload_module(module: ModuleType) -> Any:
raise ImportError(exc, traceback.format_exc()) from exc


def import_object(modname: str, objpath: list[str], objtype: str = '',
attrgetter: Callable[[Any, str], Any] = safe_getattr) -> Any:
def import_object(
modname: str,
objpath: list[str],
objtype: str = '',
attrgetter: Callable[[Any, str], Any] = safe_getattr,
) -> Any:
if objpath:
logger.debug('[autodoc] from %s import %s', modname, '.'.join(objpath))
else:
Expand All @@ -176,7 +192,9 @@ def import_object(modname: str, objpath: list[str], objtype: str = '',
original_module_names = frozenset(sys.modules)
module = import_module(modname)
if os.environ.get('SPHINX_AUTODOC_RELOAD_MODULES'):
new_modules = [m for m in sys.modules if m not in original_module_names]
new_modules = [
m for m in sys.modules if m not in original_module_names
]
# Try reloading modules with ``typing.TYPE_CHECKING == True``.
try:
typing.TYPE_CHECKING = True
Expand Down Expand Up @@ -222,8 +240,11 @@ def import_object(modname: str, objpath: list[str], objtype: str = '',
exc = exc_on_importing

if objpath:
errmsg = ('autodoc: failed to import %s %r from module %r' %
(objtype, '.'.join(objpath), modname))
errmsg = 'autodoc: failed to import %s %r from module %r' % (
objtype,
'.'.join(objpath),
modname,
)
else:
errmsg = f'autodoc: failed to import {objtype} {modname!r}'

Expand All @@ -232,14 +253,18 @@ def import_object(modname: str, objpath: list[str], objtype: str = '',
# traceback
real_exc, traceback_msg = exc.args
if isinstance(real_exc, SystemExit):
errmsg += ('; the module executes module level statement '
'and it might call sys.exit().')
errmsg += (
'; the module executes module level statement '
'and it might call sys.exit().'
)
elif isinstance(real_exc, ImportError) and real_exc.args:
errmsg += '; the following exception was raised:\n%s' % real_exc.args[0]
else:
errmsg += '; the following exception was raised:\n%s' % traceback_msg
else:
errmsg += '; the following exception was raised:\n%s' % traceback.format_exc()
errmsg += (
'; the following exception was raised:\n%s' % traceback.format_exc()
)

logger.debug(errmsg)
raise ImportError(errmsg) from exc
Expand Down Expand Up @@ -267,11 +292,17 @@ def get_object_members(

# enum members
if isenumclass(subject):
for name, defining_class, value in _filter_enum_dict(subject, attrgetter, obj_dict):
for name, defining_class, value in _filter_enum_dict(
subject, attrgetter, obj_dict
):
# the order of occurrence of *name* matches the subject's MRO,
# allowing inherited attributes to be shadowed correctly
if unmangled := unmangle(defining_class, name):
members[unmangled] = Attribute(unmangled, defining_class is subject, value)
members[unmangled] = Attribute(
name=unmangled,
directly_defined=defining_class is subject,
value=value,
)

# members in __slots__
try:
Expand All @@ -280,7 +311,9 @@ def get_object_members(
from sphinx.ext.autodoc import SLOTSATTR

for name in subject___slots__:
members[name] = Attribute(name, True, SLOTSATTR)
members[name] = Attribute(
name=name, directly_defined=True, value=SLOTSATTR
)
except (TypeError, ValueError):
pass

Expand All @@ -291,7 +324,9 @@ def get_object_members(
directly_defined = name in obj_dict
unmangled = unmangle(subject, name)
if unmangled and unmangled not in members:
members[unmangled] = Attribute(unmangled, directly_defined, value)
members[unmangled] = Attribute(
name=unmangled, directly_defined=directly_defined, value=value
)
except AttributeError:
continue

Expand All @@ -300,20 +335,25 @@ def get_object_members(
for name in getannotations(cls):
unmangled = unmangle(cls, name)
if unmangled and unmangled not in members:
members[unmangled] = Attribute(unmangled, cls is subject, INSTANCEATTR)
members[unmangled] = Attribute(
name=unmangled, directly_defined=cls is subject, value=INSTANCEATTR
)

if analyzer:
# append instance attributes (cf. self.attr1) if analyzer knows
namespace = '.'.join(objpath)
for (ns, name) in analyzer.find_attr_docs():
for ns, name in analyzer.find_attr_docs():
if namespace == ns and name not in members:
members[name] = Attribute(name, True, INSTANCEATTR)
members[name] = Attribute(
name=name, directly_defined=True, value=INSTANCEATTR
)

return members


def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
inherit_docstrings: bool = True) -> dict[str, ObjectMember]:
def get_class_members(
subject: Any, objpath: Any, attrgetter: Callable, inherit_docstrings: bool = True
) -> dict[str, ObjectMember]:
"""Get members and attributes of target class."""
from sphinx.ext.autodoc import INSTANCEATTR, ObjectMember

Expand All @@ -324,11 +364,15 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,

# enum members
if isenumclass(subject):
for name, defining_class, value in _filter_enum_dict(subject, attrgetter, obj_dict):
for name, defining_class, value in _filter_enum_dict(
subject, attrgetter, obj_dict
):
# the order of occurrence of *name* matches the subject's MRO,
# allowing inherited attributes to be shadowed correctly
if unmangled := unmangle(defining_class, name):
members[unmangled] = ObjectMember(unmangled, value, class_=defining_class)
members[unmangled] = ObjectMember(
unmangled, value, class_=defining_class
)

# members in __slots__
try:
Expand All @@ -337,8 +381,9 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
from sphinx.ext.autodoc import SLOTSATTR

for name, docstring in subject___slots__.items():
members[name] = ObjectMember(name, SLOTSATTR, class_=subject,
docstring=docstring)
members[name] = ObjectMember(
name, SLOTSATTR, class_=subject, docstring=docstring
)
except (TypeError, ValueError):
pass

Expand Down Expand Up @@ -380,19 +425,27 @@ def get_class_members(subject: Any, objpath: Any, attrgetter: Callable,
else:
docstring = None

members[unmangled] = ObjectMember(unmangled, INSTANCEATTR, class_=cls,
docstring=docstring)
members[unmangled] = ObjectMember(
unmangled, INSTANCEATTR, class_=cls, docstring=docstring
)

# append or complete instance attributes (cf. self.attr1) if analyzer knows
if analyzer:
for (ns, name), docstring in analyzer.attr_docs.items():
if ns == qualname and name not in members:
# otherwise unknown instance attribute
members[name] = ObjectMember(name, INSTANCEATTR, class_=cls,
docstring='\n'.join(docstring))
elif (ns == qualname and docstring and
isinstance(members[name], ObjectMember) and
not members[name].docstring):
members[name] = ObjectMember(
name,
INSTANCEATTR,
class_=cls,
docstring='\n'.join(docstring),
)
elif (
ns == qualname
and docstring
and isinstance(members[name], ObjectMember)
and not members[name].docstring
):
if cls != subject and not inherit_docstrings:
# If we are in the MRO of the class and not the class itself,
# and we do not want to inherit docstrings, then skip setting
Expand Down
Loading

0 comments on commit e81c4e8

Please sign in to comment.