From 5a7117f6a9585ada939123d29bb5f1b50d07c15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 30 Dec 2024 18:16:54 +0100 Subject: [PATCH 1/4] fix ordering of class/static methods --- sphinx/ext/autodoc/__init__.py | 53 +++++++++++++++-------- tests/test_extensions/test_ext_autodoc.py | 6 ++- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 60c31e2542e..ad84a157114 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -434,8 +434,7 @@ def parse_name(self) -> bool: self.modname = modname self.args = args self.retann = retann - self.fullname = ((self.modname or '') + - ('.' + '.'.join(self.objpath) if self.objpath else '')) + self.fullname = '.'.join([self.modname, *self.objpath]) return True def import_object(self, raiseerror: bool = False) -> bool: @@ -832,7 +831,7 @@ def document_members(self, all_members: bool = False) -> None: members_check_module, members = self.get_object_members(want_all) # document non-skipped members - memberdocumenters: list[tuple[Documenter, bool]] = [] + member_documenters: list[tuple[Documenter, bool]] = [] for (mname, member, isattr) in self.filter_members(members, want_all): classes = [cls for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self)] @@ -845,13 +844,26 @@ def document_members(self, all_members: bool = False) -> None: # of inner classes can be documented full_mname = f'{self.modname}::' + '.'.join((*self.objpath, mname)) documenter = classes[-1](self.directive, full_mname, self.indent) - memberdocumenters.append((documenter, isattr)) + member_documenters.append((documenter, isattr)) member_order = self.options.member_order or self.config.autodoc_member_order - memberdocumenters = self.sort_members(memberdocumenters, member_order) - - for documenter, isattr in memberdocumenters: - documenter.generate( + # We now try to import all objects before ordering them. This is to + # avoid possible circular imports if we were to import objects after + # their associated documenters have been sorted. + member_documenters = [ + (documenter, isattr) for documenter, isattr in member_documenters + if documenter.parse_name() and documenter.import_object() + ] + member_documenters = self.sort_members(member_documenters, member_order) + + for documenter, isattr in member_documenters: + assert documenter.modname + # We can directly call ._generate() since the documenters + # already called parse_name() and import_object() before. + # + # Note that those two methods above do not emit events, so + # whatever objects we deduced should not have changed. + documenter._generate( all_members=True, real_modname=self.real_modname, check_module=members_check_module and not isattr) @@ -909,6 +921,15 @@ def generate( if not self.import_object(): return + self._generate(more_content, real_modname, check_module, all_members) + + def _generate( + self, + more_content: StringList | None = None, + real_modname: str | None = None, + check_module: bool = False, + all_members: bool = False, + ) -> None: # If there is no real module defined, figure out which to use. # The real module is used in the module analyzer to look up the module # where the attribute documentation would actually be found in. @@ -2177,15 +2198,13 @@ def import_object(self, raiseerror: bool = False) -> bool: return ret # to distinguish classmethod/staticmethod - obj = self.parent.__dict__.get(self.object_name) - if obj is None: - obj = self.object - - if (inspect.isclassmethod(obj) or - inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name)): - # document class and static members before ordinary ones - self.member_order = self.member_order - 1 - + obj = self.parent.__dict__.get(self.object_name, self.object) + if inspect.isstaticmethod(obj, cls=self.parent, name=self.object_name): + # document static members before class and regular methods + self.member_order -= 2 + elif inspect.isclassmethod(obj): + # document class methods before regular methods + self.member_order -= 1 return ret def format_args(self, **kwargs: Any) -> str: diff --git a/tests/test_extensions/test_ext_autodoc.py b/tests/test_extensions/test_ext_autodoc.py index eb775aba081..52e39faf6db 100644 --- a/tests/test_extensions/test_ext_autodoc.py +++ b/tests/test_extensions/test_ext_autodoc.py @@ -1215,10 +1215,12 @@ def test_autodoc_member_order(app): actual = do_autodoc(app, 'class', 'target.Class', options) assert list(filter(lambda l: '::' in l, actual)) == [ '.. py:class:: Class(arg)', - ' .. py:method:: Class.excludemeth()', - ' .. py:method:: Class.meth()', + # class methods ' .. py:method:: Class.moore(a, e, f) -> happiness', ' .. py:method:: Class.roger(a, *, b=2, c=3, d=4, e=5, f=6)', + # regular methods + ' .. py:method:: Class.excludemeth()', + ' .. py:method:: Class.meth()', ' .. py:method:: Class.skipmeth()', ' .. py:method:: Class.undocmeth()', ' .. py:attribute:: Class._private_inst_attr', From 459bdd9e8489ae5a2a64ea1daaa35c98164a42d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:23:34 +0100 Subject: [PATCH 2/4] fix lint --- sphinx/ext/autodoc/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 16138b507f6..379845f2bef 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -895,7 +895,8 @@ def document_members(self, all_members: bool = False) -> None: member_documenters: list[tuple[Documenter, bool]] = [] for mname, member, isattr in self.filter_members(members, want_all): classes = [ - cls for cls in self.documenters.values() + cls + for cls in self.documenters.values() if cls.can_document_member(member, mname, isattr, self) ] if not classes: From 60c9735d56beacfd970a2bf7fe0c84fd0dc19709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Mon, 6 Jan 2025 12:23:47 +0100 Subject: [PATCH 3/4] fix lint --- sphinx/ext/autodoc/__init__.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 379845f2bef..d83ce0246d1 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -915,7 +915,8 @@ def document_members(self, all_members: bool = False) -> None: # avoid possible circular imports if we were to import objects after # their associated documenters have been sorted. member_documenters = [ - (documenter, isattr) for documenter, isattr in member_documenters + (documenter, isattr) + for documenter, isattr in member_documenters if documenter.parse_name() and documenter.import_object() ] member_documenters = self.sort_members(member_documenters, member_order) From 200f5a9282f7919b0ec14dd82e51904a231da043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?B=C3=A9n=C3=A9dikt=20Tran?= <10796600+picnixz@users.noreply.github.com> Date: Sun, 19 Jan 2025 10:25:33 +0100 Subject: [PATCH 4/4] revert some changes --- sphinx/ext/autodoc/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 0ed1486f8c1..d430b86f60a 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -471,7 +471,7 @@ def parse_name(self) -> bool: self.modname = modname self.args = args self.retann = retann - self.fullname = '.'.join([self.modname, *self.objpath]) + self.fullname = '.'.join((self.modname or '', *self.objpath)) return True def import_object(self, raiseerror: bool = False) -> bool: