From 6c6fed5e55117a9da8a37bdeffcb5a14d040c62e Mon Sep 17 00:00:00 2001 From: Takeshi KOMIYA Date: Fri, 24 Dec 2021 01:48:22 +0900 Subject: [PATCH] Fix #10009: autodoc: Crashes if subject raises an error on getdoc() --- CHANGES | 1 + sphinx/ext/autodoc/__init__.py | 174 +++++++++++++++++---------------- 2 files changed, 89 insertions(+), 86 deletions(-) diff --git a/CHANGES b/CHANGES index 77a901e853..03cd39ba7a 100644 --- a/CHANGES +++ b/CHANGES @@ -46,6 +46,7 @@ Bugs fixed with Python 3.10 * #9968: autodoc: instance variables are not shown if __init__ method has position-only-arguments +* #10009: autodoc: Crashes if target object raises an error on getting docstring * #9947: i18n: topic directive having a bullet list can't be translatable * #9878: mathjax: MathJax configuration is placed after loading MathJax itself * #9857: Generated RFC links use outdated base url diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index 5ada06a6a5..16f75e1d39 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -711,109 +711,111 @@ def is_filtered_inherited_member(name: str, obj: Any) -> bool: # process members and determine which to skip for obj in members: - membername, member = obj - # if isattr is True, the member is documented as an attribute - if member is INSTANCEATTR: - isattr = True - elif (namespace, membername) in attr_docs: - isattr = True - else: - isattr = False - - doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings, - self.object, membername) - if not isinstance(doc, str): - # Ignore non-string __doc__ - doc = None - - # if the member __doc__ is the same as self's __doc__, it's just - # inherited and therefore not the member's doc - cls = self.get_attr(member, '__class__', None) - if cls: - cls_doc = self.get_attr(cls, '__doc__', None) - if cls_doc == doc: - doc = None - - if isinstance(obj, ObjectMember) and obj.docstring: - # hack for ClassDocumenter to inject docstring via ObjectMember - doc = obj.docstring + try: + membername, member = obj + # if isattr is True, the member is documented as an attribute + if member is INSTANCEATTR: + isattr = True + elif (namespace, membername) in attr_docs: + isattr = True + else: + isattr = False - doc, metadata = separate_metadata(doc) - has_doc = bool(doc) + doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings, + self.object, membername) + if not isinstance(doc, str): + # Ignore non-string __doc__ + doc = None - if 'private' in metadata: - # consider a member private if docstring has "private" metadata - isprivate = True - elif 'public' in metadata: - # consider a member public if docstring has "public" metadata - isprivate = False - else: - isprivate = membername.startswith('_') + # if the member __doc__ is the same as self's __doc__, it's just + # inherited and therefore not the member's doc + cls = self.get_attr(member, '__class__', None) + if cls: + cls_doc = self.get_attr(cls, '__doc__', None) + if cls_doc == doc: + doc = None + + if isinstance(obj, ObjectMember) and obj.docstring: + # hack for ClassDocumenter to inject docstring via ObjectMember + doc = obj.docstring + + doc, metadata = separate_metadata(doc) + has_doc = bool(doc) + + if 'private' in metadata: + # consider a member private if docstring has "private" metadata + isprivate = True + elif 'public' in metadata: + # consider a member public if docstring has "public" metadata + isprivate = False + else: + isprivate = membername.startswith('_') - keep = False - if ismock(member) and (namespace, membername) not in attr_docs: - # mocked module or object - pass - elif self.options.exclude_members and membername in self.options.exclude_members: - # remove members given by exclude-members keep = False - elif want_all and special_member_re.match(membername): - # special __methods__ - if self.options.special_members and membername in self.options.special_members: - if membername == '__doc__': - keep = False - elif is_filtered_inherited_member(membername, obj): - keep = False - else: - keep = has_doc or self.options.undoc_members - else: + if ismock(member) and (namespace, membername) not in attr_docs: + # mocked module or object + pass + elif (self.options.exclude_members and + membername in self.options.exclude_members): + # remove members given by exclude-members keep = False - elif (namespace, membername) in attr_docs: - if want_all and isprivate: - if self.options.private_members is None: + elif want_all and special_member_re.match(membername): + # special __methods__ + if (self.options.special_members and + membername in self.options.special_members): + if membername == '__doc__': + keep = False + elif is_filtered_inherited_member(membername, obj): + keep = False + else: + keep = has_doc or self.options.undoc_members + else: keep = False + elif (namespace, membername) in attr_docs: + if want_all and isprivate: + if self.options.private_members is None: + keep = False + else: + keep = membername in self.options.private_members + else: + # keep documented attributes + keep = True + elif want_all and isprivate: + if has_doc or self.options.undoc_members: + if self.options.private_members is None: + keep = False + elif is_filtered_inherited_member(membername, obj): + keep = False + else: + keep = membername in self.options.private_members else: - keep = membername in self.options.private_members - else: - # keep documented attributes - keep = True - elif want_all and isprivate: - if has_doc or self.options.undoc_members: - if self.options.private_members is None: keep = False - elif is_filtered_inherited_member(membername, obj): + else: + if (self.options.members is ALL and + is_filtered_inherited_member(membername, obj)): keep = False else: - keep = membername in self.options.private_members - else: - keep = False - else: - if (self.options.members is ALL and - is_filtered_inherited_member(membername, obj)): - keep = False - else: - # ignore undocumented members if :undoc-members: is not given - keep = has_doc or self.options.undoc_members + # ignore undocumented members if :undoc-members: is not given + keep = has_doc or self.options.undoc_members - if isinstance(obj, ObjectMember) and obj.skipped: - # forcedly skipped member (ex. a module attribute not defined in __all__) - keep = False + if isinstance(obj, ObjectMember) and obj.skipped: + # forcedly skipped member (ex. a module attribute not defined in __all__) + keep = False - # give the user a chance to decide whether this member - # should be skipped - if self.env.app: - # let extensions preprocess docstrings - try: + # give the user a chance to decide whether this member + # should be skipped + if self.env.app: + # let extensions preprocess docstrings skip_user = self.env.app.emit_firstresult( 'autodoc-skip-member', self.objtype, membername, member, not keep, self.options) if skip_user is not None: keep = not skip_user - except Exception as exc: - logger.warning(__('autodoc: failed to determine %r to be documented, ' - 'the following exception was raised:\n%s'), - member, exc, type='autodoc') - keep = False + except Exception as exc: + logger.warning(__('autodoc: failed to determine %r to be documented, ' + 'the following exception was raised:\n%s'), + member, exc, type='autodoc') + keep = False if keep: ret.append((membername, member, isattr))