Skip to content

Commit be84da0

Browse files
authored
Merge pull request #10011 from tk0miya/10009_exception_on_getdoc
Fix #10009: autodoc: Crashes if subject raises an error on getdoc()
2 parents 8600f43 + b1c99e8 commit be84da0

File tree

2 files changed

+89
-86
lines changed

2 files changed

+89
-86
lines changed

CHANGES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Bugs fixed
5252
* #9968: autodoc: instance variables are not shown if __init__ method has
5353
position-only-arguments
5454
* #9194: autodoc: types under the "typing" module are not hyperlinked
55+
* #10009: autodoc: Crashes if target object raises an error on getting docstring
5556
* #9947: i18n: topic directive having a bullet list can't be translatable
5657
* #9878: mathjax: MathJax configuration is placed after loading MathJax itself
5758
* #9857: Generated RFC links use outdated base url

sphinx/ext/autodoc/__init__.py

Lines changed: 88 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -711,109 +711,111 @@ def is_filtered_inherited_member(name: str, obj: Any) -> bool:
711711

712712
# process members and determine which to skip
713713
for obj in members:
714-
membername, member = obj
715-
# if isattr is True, the member is documented as an attribute
716-
if member is INSTANCEATTR:
717-
isattr = True
718-
elif (namespace, membername) in attr_docs:
719-
isattr = True
720-
else:
721-
isattr = False
722-
723-
doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings,
724-
self.object, membername)
725-
if not isinstance(doc, str):
726-
# Ignore non-string __doc__
727-
doc = None
728-
729-
# if the member __doc__ is the same as self's __doc__, it's just
730-
# inherited and therefore not the member's doc
731-
cls = self.get_attr(member, '__class__', None)
732-
if cls:
733-
cls_doc = self.get_attr(cls, '__doc__', None)
734-
if cls_doc == doc:
735-
doc = None
736-
737-
if isinstance(obj, ObjectMember) and obj.docstring:
738-
# hack for ClassDocumenter to inject docstring via ObjectMember
739-
doc = obj.docstring
714+
try:
715+
membername, member = obj
716+
# if isattr is True, the member is documented as an attribute
717+
if member is INSTANCEATTR:
718+
isattr = True
719+
elif (namespace, membername) in attr_docs:
720+
isattr = True
721+
else:
722+
isattr = False
740723

741-
doc, metadata = separate_metadata(doc)
742-
has_doc = bool(doc)
724+
doc = getdoc(member, self.get_attr, self.config.autodoc_inherit_docstrings,
725+
self.object, membername)
726+
if not isinstance(doc, str):
727+
# Ignore non-string __doc__
728+
doc = None
743729

744-
if 'private' in metadata:
745-
# consider a member private if docstring has "private" metadata
746-
isprivate = True
747-
elif 'public' in metadata:
748-
# consider a member public if docstring has "public" metadata
749-
isprivate = False
750-
else:
751-
isprivate = membername.startswith('_')
730+
# if the member __doc__ is the same as self's __doc__, it's just
731+
# inherited and therefore not the member's doc
732+
cls = self.get_attr(member, '__class__', None)
733+
if cls:
734+
cls_doc = self.get_attr(cls, '__doc__', None)
735+
if cls_doc == doc:
736+
doc = None
737+
738+
if isinstance(obj, ObjectMember) and obj.docstring:
739+
# hack for ClassDocumenter to inject docstring via ObjectMember
740+
doc = obj.docstring
741+
742+
doc, metadata = separate_metadata(doc)
743+
has_doc = bool(doc)
744+
745+
if 'private' in metadata:
746+
# consider a member private if docstring has "private" metadata
747+
isprivate = True
748+
elif 'public' in metadata:
749+
# consider a member public if docstring has "public" metadata
750+
isprivate = False
751+
else:
752+
isprivate = membername.startswith('_')
752753

753-
keep = False
754-
if ismock(member) and (namespace, membername) not in attr_docs:
755-
# mocked module or object
756-
pass
757-
elif self.options.exclude_members and membername in self.options.exclude_members:
758-
# remove members given by exclude-members
759754
keep = False
760-
elif want_all and special_member_re.match(membername):
761-
# special __methods__
762-
if self.options.special_members and membername in self.options.special_members:
763-
if membername == '__doc__':
764-
keep = False
765-
elif is_filtered_inherited_member(membername, obj):
766-
keep = False
767-
else:
768-
keep = has_doc or self.options.undoc_members
769-
else:
755+
if ismock(member) and (namespace, membername) not in attr_docs:
756+
# mocked module or object
757+
pass
758+
elif (self.options.exclude_members and
759+
membername in self.options.exclude_members):
760+
# remove members given by exclude-members
770761
keep = False
771-
elif (namespace, membername) in attr_docs:
772-
if want_all and isprivate:
773-
if self.options.private_members is None:
762+
elif want_all and special_member_re.match(membername):
763+
# special __methods__
764+
if (self.options.special_members and
765+
membername in self.options.special_members):
766+
if membername == '__doc__':
767+
keep = False
768+
elif is_filtered_inherited_member(membername, obj):
769+
keep = False
770+
else:
771+
keep = has_doc or self.options.undoc_members
772+
else:
774773
keep = False
774+
elif (namespace, membername) in attr_docs:
775+
if want_all and isprivate:
776+
if self.options.private_members is None:
777+
keep = False
778+
else:
779+
keep = membername in self.options.private_members
780+
else:
781+
# keep documented attributes
782+
keep = True
783+
elif want_all and isprivate:
784+
if has_doc or self.options.undoc_members:
785+
if self.options.private_members is None:
786+
keep = False
787+
elif is_filtered_inherited_member(membername, obj):
788+
keep = False
789+
else:
790+
keep = membername in self.options.private_members
775791
else:
776-
keep = membername in self.options.private_members
777-
else:
778-
# keep documented attributes
779-
keep = True
780-
elif want_all and isprivate:
781-
if has_doc or self.options.undoc_members:
782-
if self.options.private_members is None:
783792
keep = False
784-
elif is_filtered_inherited_member(membername, obj):
793+
else:
794+
if (self.options.members is ALL and
795+
is_filtered_inherited_member(membername, obj)):
785796
keep = False
786797
else:
787-
keep = membername in self.options.private_members
788-
else:
789-
keep = False
790-
else:
791-
if (self.options.members is ALL and
792-
is_filtered_inherited_member(membername, obj)):
793-
keep = False
794-
else:
795-
# ignore undocumented members if :undoc-members: is not given
796-
keep = has_doc or self.options.undoc_members
798+
# ignore undocumented members if :undoc-members: is not given
799+
keep = has_doc or self.options.undoc_members
797800

798-
if isinstance(obj, ObjectMember) and obj.skipped:
799-
# forcedly skipped member (ex. a module attribute not defined in __all__)
800-
keep = False
801+
if isinstance(obj, ObjectMember) and obj.skipped:
802+
# forcedly skipped member (ex. a module attribute not defined in __all__)
803+
keep = False
801804

802-
# give the user a chance to decide whether this member
803-
# should be skipped
804-
if self.env.app:
805-
# let extensions preprocess docstrings
806-
try:
805+
# give the user a chance to decide whether this member
806+
# should be skipped
807+
if self.env.app:
808+
# let extensions preprocess docstrings
807809
skip_user = self.env.app.emit_firstresult(
808810
'autodoc-skip-member', self.objtype, membername, member,
809811
not keep, self.options)
810812
if skip_user is not None:
811813
keep = not skip_user
812-
except Exception as exc:
813-
logger.warning(__('autodoc: failed to determine %r to be documented, '
814-
'the following exception was raised:\n%s'),
815-
member, exc, type='autodoc')
816-
keep = False
814+
except Exception as exc:
815+
logger.warning(__('autodoc: failed to determine %s.%s (%r) to be documented, '
816+
'the following exception was raised:\n%s'),
817+
self.name, membername, member, exc, type='autodoc')
818+
keep = False
817819

818820
if keep:
819821
ret.append((membername, member, isattr))

0 commit comments

Comments
 (0)