Fix #10009: autodoc: Crashes if subject raises an error on getdoc()

This commit is contained in:
Takeshi KOMIYA 2021-12-24 01:48:22 +09:00
parent 6ad6594ec7
commit 6c6fed5e55
2 changed files with 88 additions and 85 deletions

View File

@ -46,6 +46,7 @@ Bugs fixed
with Python 3.10 with Python 3.10
* #9968: autodoc: instance variables are not shown if __init__ method has * #9968: autodoc: instance variables are not shown if __init__ method has
position-only-arguments 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 * #9947: i18n: topic directive having a bullet list can't be translatable
* #9878: mathjax: MathJax configuration is placed after loading MathJax itself * #9878: mathjax: MathJax configuration is placed after loading MathJax itself
* #9857: Generated RFC links use outdated base url * #9857: Generated RFC links use outdated base url

View File

@ -711,109 +711,111 @@ class Documenter:
# process members and determine which to skip # process members and determine which to skip
for obj in members: for obj in members:
membername, member = obj try:
# if isattr is True, the member is documented as an attribute membername, member = obj
if member is INSTANCEATTR: # if isattr is True, the member is documented as an attribute
isattr = True if member is INSTANCEATTR:
elif (namespace, membername) in attr_docs: isattr = True
isattr = True elif (namespace, membername) in attr_docs:
else: isattr = True
isattr = False else:
isattr = False
doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings, doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings,
self.object, membername) self.object, membername)
if not isinstance(doc, str): if not isinstance(doc, str):
# Ignore non-string __doc__ # 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 doc = None
if isinstance(obj, ObjectMember) and obj.docstring: # if the member __doc__ is the same as self's __doc__, it's just
# hack for ClassDocumenter to inject docstring via ObjectMember # inherited and therefore not the member's doc
doc = obj.docstring cls = self.get_attr(member, '__class__', None)
if cls:
cls_doc = self.get_attr(cls, '__doc__', None)
if cls_doc == doc:
doc = None
doc, metadata = separate_metadata(doc) if isinstance(obj, ObjectMember) and obj.docstring:
has_doc = bool(doc) # hack for ClassDocumenter to inject docstring via ObjectMember
doc = obj.docstring
if 'private' in metadata: doc, metadata = separate_metadata(doc)
# consider a member private if docstring has "private" metadata has_doc = bool(doc)
isprivate = True
elif 'public' in metadata: if 'private' in metadata:
# consider a member public if docstring has "public" metadata # consider a member private if docstring has "private" metadata
isprivate = False isprivate = True
else: elif 'public' in metadata:
isprivate = membername.startswith('_') # 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 keep = False
elif want_all and special_member_re.match(membername): if ismock(member) and (namespace, membername) not in attr_docs:
# special __methods__ # mocked module or object
if self.options.special_members and membername in self.options.special_members: pass
if membername == '__doc__': 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:
keep = False keep = False
elif is_filtered_inherited_member(membername, obj): 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 = False
else:
if (self.options.members is ALL and
is_filtered_inherited_member(membername, obj)):
keep = False keep = False
else: else:
# ignore undocumented members if :undoc-members: is not given
keep = has_doc or self.options.undoc_members 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 = 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
if isinstance(obj, ObjectMember) and obj.skipped: if isinstance(obj, ObjectMember) and obj.skipped:
# forcedly skipped member (ex. a module attribute not defined in __all__) # forcedly skipped member (ex. a module attribute not defined in __all__)
keep = False keep = False
# give the user a chance to decide whether this member # give the user a chance to decide whether this member
# should be skipped # should be skipped
if self.env.app: if self.env.app:
# let extensions preprocess docstrings # let extensions preprocess docstrings
try:
skip_user = self.env.app.emit_firstresult( skip_user = self.env.app.emit_firstresult(
'autodoc-skip-member', self.objtype, membername, member, 'autodoc-skip-member', self.objtype, membername, member,
not keep, self.options) not keep, self.options)
if skip_user is not None: if skip_user is not None:
keep = not skip_user keep = not skip_user
except Exception as exc: except Exception as exc:
logger.warning(__('autodoc: failed to determine %r to be documented, ' logger.warning(__('autodoc: failed to determine %r to be documented, '
'the following exception was raised:\n%s'), 'the following exception was raised:\n%s'),
member, exc, type='autodoc') member, exc, type='autodoc')
keep = False keep = False
if keep: if keep:
ret.append((membername, member, isattr)) ret.append((membername, member, isattr))