Skip to content

Commit 6083b5a

Browse files
authored
Move member merging logic from options to _member_finder (#13982)
1 parent 90192fc commit 6083b5a

File tree

7 files changed

+62
-58
lines changed

7 files changed

+62
-58
lines changed

sphinx/ext/autodoc/_directive_options.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,6 @@ def copy(self) -> Self:
8989
def from_directive_options(cls, opts: Mapping[str, Any], /) -> Self:
9090
return cls(**{k.replace('-', '_'): v for k, v in opts.items() if v is not None})
9191

92-
def merge_member_options(self) -> Self:
93-
"""Merge :private-members: and :special-members: into :members:"""
94-
if self.members is ALL:
95-
# merging is not needed when members: ALL
96-
return self
97-
98-
members = self.members or []
99-
for others in self.private_members, self.special_members:
100-
if others is not None and others is not ALL:
101-
members.extend(others)
102-
new = self.copy()
103-
new.members = list(dict.fromkeys(members)) # deduplicate; preserve order
104-
return new
105-
10692

10793
def identity(x: Any) -> Any:
10894
return x

sphinx/ext/autodoc/_documenters.py

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -100,16 +100,6 @@ def __init__(
100100
# the module analyzer to get at attribute docs, or None
101101
self.analyzer: ModuleAnalyzer | None = None
102102

103-
if isinstance(self, ModuleDocumenter):
104-
self.options = self.options.merge_member_options()
105-
elif isinstance(self, ClassDocumenter):
106-
if self.config.autodoc_class_signature == 'separated':
107-
# show __init__() method
108-
if self.options.special_members is None:
109-
self.options.special_members = []
110-
self.options.special_members += ['__new__', '__init__']
111-
self.options = self.options.merge_member_options()
112-
113103
def add_line(self, line: str, source: str, *lineno: int, indent: str) -> None:
114104
"""Append one line of generated reST to the output."""
115105
if line.strip(): # not a blank line

sphinx/ext/autodoc/_member_finder.py

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -193,10 +193,13 @@ def _gather_members(
193193
found_members = _get_members_to_document(
194194
want_all=want_all,
195195
get_attr=get_attr,
196+
class_signature=config.autodoc_class_signature,
196197
inherit_docstrings=config.autodoc_inherit_docstrings,
197198
props=props,
198199
opt_members=options.members or (),
199200
inherited_members=inherited_members,
201+
opt_private_members=options.private_members,
202+
opt_special_members=options.special_members,
200203
ignore_module_all=bool(options.ignore_module_all),
201204
attr_docs=attr_docs,
202205
)
@@ -205,6 +208,7 @@ def _gather_members(
205208
want_all=want_all,
206209
events=events,
207210
get_attr=get_attr,
211+
class_signature=config.autodoc_class_signature,
208212
inherit_docstrings=config.autodoc_inherit_docstrings,
209213
options=options,
210214
props=props,
@@ -229,12 +233,10 @@ def _gather_members(
229233
if not obj_type:
230234
# don't know how to document this member
231235
continue
232-
doccls = registry.documenters[obj_type]
233236
# give explicitly separated module name, so that members
234237
# of inner classes can be documented
235238
dotted_parts = '.'.join((*props.parts, member_name))
236239
full_name = f'{props.module_name}::{dotted_parts}'
237-
documenter = doccls(directive, full_name, indent)
238240

239241
# We now try to import all objects before ordering them. This is to
240242
# avoid possible circular imports if we were to import objects after
@@ -249,10 +251,13 @@ def _gather_members(
249251
env=env,
250252
events=events,
251253
get_attr=get_attr,
252-
options=documenter.options,
254+
options=directive.genopt,
253255
)
254256
if member_props is None:
255257
continue
258+
259+
doccls = registry.documenters[obj_type]
260+
documenter = doccls(directive, full_name, indent)
256261
documenter.props = member_props
257262

258263
member_documenters.append((documenter, is_attr))
@@ -277,10 +282,13 @@ def _get_members_to_document(
277282
*,
278283
want_all: bool,
279284
get_attr: _AttrGetter,
285+
class_signature: Literal['mixed', 'separated'],
280286
inherit_docstrings: bool,
281287
props: _ModuleProperties | _ClassDefProperties,
282288
opt_members: ALL_T | Sequence[str],
283289
inherited_members: Set[str],
290+
opt_private_members: ALL_T | Sequence[str] | None,
291+
opt_special_members: ALL_T | Sequence[str] | None,
284292
ignore_module_all: bool,
285293
attr_docs: dict[tuple[str, str], list[str]],
286294
) -> list[ObjectMember]:
@@ -315,7 +323,16 @@ def _get_members_to_document(
315323
else:
316324
# specific members given
317325
assert opt_members is not ALL
318-
wanted_members = frozenset(opt_members)
326+
327+
# Merge :private-members: and :special-members: into :members:
328+
combined_members = set(opt_members)
329+
if opt_private_members is not None and opt_private_members is not ALL:
330+
combined_members.update(opt_private_members)
331+
if opt_special_members is not None and opt_special_members is not ALL:
332+
combined_members.update(opt_special_members)
333+
if class_signature == 'separated' and props.obj_type in {'class', 'exception'}:
334+
combined_members |= {'__new__', '__init__'} # show __init__() method
335+
wanted_members = frozenset(combined_members)
319336

320337
object_members_map: dict[str, ObjectMember] = {}
321338
if props.obj_type == 'module':
@@ -489,6 +506,7 @@ def _filter_members(
489506
get_attr: _AttrGetter,
490507
options: _AutoDocumenterOptions,
491508
props: _ModuleProperties | _ClassDefProperties,
509+
class_signature: Literal['mixed', 'separated'],
492510
inherit_docstrings: bool,
493511
inherited_members: Set[str],
494512
exclude_members: EMPTY_T | Set[str] | None,
@@ -513,6 +531,7 @@ def _filter_members(
513531
member_cls=obj.class_,
514532
get_attr=get_attr,
515533
has_attr_doc=has_attr_doc,
534+
class_signature=class_signature,
516535
inherit_docstrings=inherit_docstrings,
517536
inherited_members=inherited_members,
518537
parent=props._obj,
@@ -793,6 +812,7 @@ def _should_keep_member(
793812
member_cls: Any,
794813
get_attr: _AttrGetter,
795814
has_attr_doc: bool,
815+
class_signature: Literal['mixed', 'separated'],
796816
inherit_docstrings: bool,
797817
inherited_members: Set[str],
798818
parent: Any,
@@ -862,12 +882,16 @@ def _should_keep_member(
862882

863883
if special_member_re.match(member_name):
864884
# special __methods__
885+
if member_name == '__doc__' or is_filtered_inherited_member:
886+
return False
865887
if special_members and member_name in special_members:
866-
if member_name == '__doc__': # NoQA: SIM114
867-
return False
868-
elif is_filtered_inherited_member:
869-
return False
870888
return has_doc
889+
if (
890+
class_signature == 'separated'
891+
and member_name in {'__new__', '__init__'}
892+
and inspect.isclass(parent)
893+
):
894+
return has_doc # show __init__() method
871895
return False
872896

873897
if is_private:

sphinx/ext/autodoc/directive.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -142,10 +142,6 @@ def run(self) -> list[Node]:
142142

143143
# generate the output
144144
get_attr = _AutodocAttrGetter(self.env._registry.autodoc_attrgetters)
145-
params = DocumenterBridge(
146-
self.env, reporter, documenter_options, lineno, self.state, get_attr
147-
)
148-
documenter = doccls(params, self.arguments[0])
149145
props = _load_object_by_name(
150146
name=self.arguments[0],
151147
objtype=objtype, # type: ignore[arg-type]
@@ -156,11 +152,17 @@ def run(self) -> list[Node]:
156152
env=self.env,
157153
events=self.env.events,
158154
get_attr=get_attr,
159-
options=documenter.options,
155+
options=documenter_options,
156+
)
157+
if props is None:
158+
return []
159+
160+
params = DocumenterBridge(
161+
self.env, reporter, documenter_options, lineno, self.state, get_attr
160162
)
161-
if props is not None:
162-
documenter.props = props
163-
documenter._generate(more_content=self.content)
163+
documenter = doccls(params, self.arguments[0])
164+
documenter.props = props
165+
documenter._generate(more_content=self.content)
164166
if not params.result:
165167
return []
166168

sphinx/ext/autosummary/__init__.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -369,7 +369,6 @@ def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]:
369369

370370
result = StringList() # initialize for each documenter
371371
obj_type = _get_documenter(obj, parent)
372-
doccls = env._registry.documenters[obj_type]
373372
if isinstance(obj, ModuleType):
374373
full_name = real_name
375374
else:
@@ -379,7 +378,6 @@ def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]:
379378
# NB. using full_name here is important, since Documenters
380379
# handle module prefixes slightly differently
381380
self.bridge.result = result
382-
documenter = doccls(self.bridge, full_name)
383381
props = _load_object_by_name(
384382
name=full_name,
385383
objtype=obj_type,
@@ -390,7 +388,7 @@ def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]:
390388
env=env,
391389
events=events,
392390
get_attr=get_attr,
393-
options=documenter.options,
391+
options=self.bridge.genopt,
394392
)
395393
if props is None:
396394
logger.warning(
@@ -400,7 +398,6 @@ def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]:
400398
)
401399
items.append((display_name, '', '', real_name))
402400
continue
403-
documenter.props = props
404401

405402
# try to also get a source code analyzer for attribute docs
406403
real_module = props._obj___module__ or props.module_name
@@ -413,7 +410,6 @@ def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]:
413410
logger.debug('[autodoc] module analyzer failed: %s', err)
414411
# no source file -- e.g. for builtin and C modules
415412
analyzer = None
416-
documenter.analyzer = analyzer
417413

418414
# -- Grab the signature
419415

@@ -429,6 +425,10 @@ def get_items(self, names: list[str]) -> list[tuple[str, str | None, str, str]]:
429425

430426
# -- Grab the summary
431427

428+
doccls = env._registry.documenters[obj_type]
429+
documenter = doccls(self.bridge, full_name)
430+
documenter.props = props
431+
documenter.analyzer = analyzer
432432
documenter.add_content(None, indent=documenter.indent)
433433
lines = result.data[:]
434434
if props.obj_type != 'module':

tests/test_ext_autodoc/autodoc_util.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
from typing import TYPE_CHECKING
44
from unittest.mock import Mock
55

6+
from docutils.statemachine import StringList
7+
68
from sphinx.ext.autodoc._directive_options import (
79
_AutoDocumenterOptions,
810
_process_documenter_options,
@@ -17,8 +19,6 @@
1719
if TYPE_CHECKING:
1820
from typing import Any
1921

20-
from docutils.statemachine import StringList
21-
2222
from sphinx.application import Sphinx
2323
from sphinx.ext.autodoc._property_types import _AutodocObjType
2424

@@ -38,12 +38,7 @@ def do_autodoc(
3838
default_options=app.config.autodoc_default_options,
3939
options=options,
4040
)
41-
docoptions = _AutoDocumenterOptions.from_directive_options(opts)
42-
state = Mock()
43-
bridge = DocumenterBridge(
44-
app.env, LoggingReporter(''), docoptions, 1, state, safe_getattr
45-
)
46-
documenter = doccls(bridge, name)
41+
doc_options = _AutoDocumenterOptions.from_directive_options(opts)
4742
props = _load_object_by_name(
4843
name=name,
4944
objtype=obj_type,
@@ -54,9 +49,16 @@ def do_autodoc(
5449
env=app.env,
5550
events=app.events,
5651
get_attr=safe_getattr,
57-
options=documenter.options,
52+
options=doc_options,
5853
)
54+
result = StringList()
5955
if props is not None:
56+
state = Mock()
57+
bridge = DocumenterBridge(
58+
app.env, LoggingReporter(''), doc_options, 1, state, safe_getattr
59+
)
60+
bridge.result = result
61+
documenter = doccls(bridge, name)
6062
documenter.props = props
6163
documenter._generate()
62-
return bridge.result
64+
return result

tests/test_ext_autodoc/test_ext_autodoc.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -598,8 +598,6 @@ def _special_getattr(obj, attr_name, *defargs):
598598
def _assert_getter_works(app, directive, objtype, name, *attrs):
599599
getattr_spy.clear()
600600

601-
doccls = app.registry.documenters[objtype]
602-
documenter = doccls(directive, name)
603601
props = _load_object_by_name(
604602
name=name,
605603
objtype=objtype,
@@ -610,9 +608,11 @@ def _assert_getter_works(app, directive, objtype, name, *attrs):
610608
env=app.env,
611609
events=app.events,
612610
get_attr=directive.get_attr,
613-
options=documenter.options,
611+
options=directive.genopt,
614612
)
615613
if props is not None:
614+
doccls = app.registry.documenters[objtype]
615+
documenter = doccls(directive, name)
616616
documenter.props = props
617617
documenter._generate()
618618

0 commit comments

Comments
 (0)