11from __future__ import annotations
22
3+ import operator
34from typing import TYPE_CHECKING , NewType , TypeVar
45
56from docutils .statemachine import StringList
@@ -80,8 +81,6 @@ class Documenter:
8081 objtype : ClassVar = 'object'
8182 #: indentation by which to indent the directive content
8283 content_indent : ClassVar = ' '
83- #: priority if multiple documenters return True from can_document_member
84- priority : ClassVar = 0
8584 #: order if autodoc_member_order is set to 'groupwise'
8685 member_order : ClassVar = 0
8786 #: true if the generated content may contain titles
@@ -100,14 +99,6 @@ def get_attr(self, obj: Any, name: str, *defargs: Any) -> Any:
10099 """getattr() override for types such as Zope interfaces."""
101100 return autodoc_attrgetter (obj , name , * defargs , registry = self .env ._registry )
102101
103- @classmethod
104- def can_document_member (
105- cls : type [Documenter ], member : Any , membername : str , isattr : bool , parent : Any
106- ) -> bool :
107- """Called to see if a member can be documented by this Documenter."""
108- msg = 'must be implemented in subclasses'
109- raise NotImplementedError (msg )
110-
111102 def __init__ (
112103 self , directive : DocumenterBridge , name : str , indent : str = ''
113104 ) -> None :
@@ -728,18 +719,16 @@ def _gather_members(
728719 member_documenters : list [tuple [Documenter , bool ]] = []
729720 for member_name , member , is_attr in filtered_members :
730721 # prefer the documenter with the highest priority
731- doccls = max (
732- (
733- cls
734- for cls in registry .documenters .values ()
735- if cls .can_document_member (member , member_name , is_attr , self )
736- ),
737- key = lambda cls : cls .priority ,
738- default = None ,
722+ obj_type = _best_object_type_for_member (
723+ member = member ,
724+ member_name = member_name ,
725+ is_attr = is_attr ,
726+ parent_documenter = self ,
739727 )
740- if doccls is None :
728+ if not obj_type :
741729 # don't know how to document this member
742730 continue
731+ doccls = registry .documenters [obj_type ]
743732 # give explicitly separated module name, so that members
744733 # of inner classes can be documented
745734 module_prefix = f'{ props .module_name } ::'
@@ -793,13 +782,6 @@ class ModuleDocumenter(Documenter):
793782 'noindex' : bool_option ,
794783 }
795784
796- @classmethod
797- def can_document_member (
798- cls : type [Documenter ], member : Any , membername : str , isattr : bool , parent : Any
799- ) -> bool :
800- # don't document submodules automatically
801- return False
802-
803785
804786class FunctionDocumenter (Documenter ):
805787 """Specialized Documenter subclass for functions."""
@@ -809,17 +791,6 @@ class FunctionDocumenter(Documenter):
809791 objtype = 'function'
810792 member_order = 30
811793
812- @classmethod
813- def can_document_member (
814- cls : type [Documenter ], member : Any , membername : str , isattr : bool , parent : Any
815- ) -> bool :
816- # supports functions, builtins and bound methods exported at the module level
817- return (
818- inspect .isfunction (member )
819- or inspect .isbuiltin (member )
820- or (inspect .isroutine (member ) and isinstance (parent , ModuleDocumenter ))
821- )
822-
823794
824795class DecoratorDocumenter (FunctionDocumenter ):
825796 """Specialized Documenter subclass for decorator functions."""
@@ -828,9 +799,6 @@ class DecoratorDocumenter(FunctionDocumenter):
828799
829800 objtype = 'decorator'
830801
831- # must be lower than FunctionDocumenter
832- priority = FunctionDocumenter .priority - 1
833-
834802
835803class ClassDocumenter (Documenter ):
836804 """Specialized Documenter subclass for classes."""
@@ -854,19 +822,6 @@ class ClassDocumenter(Documenter):
854822 'noindex' : bool_option ,
855823 }
856824
857- # Must be higher than FunctionDocumenter, ClassDocumenter, and
858- # AttributeDocumenter as NewType can be an attribute and is a class
859- # after Python 3.10.
860- priority = 15
861-
862- @classmethod
863- def can_document_member (
864- cls : type [Documenter ], member : Any , membername : str , isattr : bool , parent : Any
865- ) -> bool :
866- return isinstance (member , type ) or (
867- isattr and isinstance (member , (NewType , TypeVar ))
868- )
869-
870825 def get_canonical_fullname (self ) -> str | None :
871826 __modname__ = safe_getattr (
872827 self .props ._obj , '__module__' , self .props .module_name
@@ -892,25 +847,6 @@ class ExceptionDocumenter(ClassDocumenter):
892847 objtype = 'exception'
893848 member_order = 10
894849
895- # needs a higher priority than ClassDocumenter
896- priority = ClassDocumenter .priority + 5
897-
898- @classmethod
899- def can_document_member (
900- cls : type [Documenter ], member : Any , membername : str , isattr : bool , parent : Any
901- ) -> bool :
902- try :
903- return isinstance (member , type ) and issubclass (member , BaseException )
904- except TypeError as exc :
905- # It's possible for a member to be considered a type, but fail
906- # issubclass checks due to not being a class. For example:
907- # https://github.com/sphinx-doc/sphinx/issues/11654#issuecomment-1696790436
908- msg = (
909- f'{ cls .__name__ } failed to discern if member { member } with'
910- f' membername { membername } is a BaseException subclass.'
911- )
912- raise ValueError (msg ) from exc
913-
914850
915851class DataDocumenter (Documenter ):
916852 """Specialized Documenter subclass for data items."""
@@ -921,17 +857,10 @@ class DataDocumenter(Documenter):
921857
922858 objtype = 'data'
923859 member_order = 40
924- priority = - 10
925860 option_spec : ClassVar [OptionSpec ] = dict (Documenter .option_spec )
926861 option_spec ['annotation' ] = annotation_option
927862 option_spec ['no-value' ] = bool_option
928863
929- @classmethod
930- def can_document_member (
931- cls : type [Documenter ], member : Any , membername : str , isattr : bool , parent : Any
932- ) -> bool :
933- return isinstance (parent , ModuleDocumenter ) and isattr
934-
935864
936865class MethodDocumenter (Documenter ):
937866 """Specialized Documenter subclass for methods (normal, static and class)."""
@@ -941,13 +870,6 @@ class MethodDocumenter(Documenter):
941870 objtype = 'method'
942871 directivetype = 'method'
943872 member_order = 50
944- priority = 1 # must be more than FunctionDocumenter
945-
946- @classmethod
947- def can_document_member (
948- cls : type [Documenter ], member : Any , membername : str , isattr : bool , parent : Any
949- ) -> bool :
950- return inspect .isroutine (member ) and not isinstance (parent , ModuleDocumenter )
951873
952874
953875class AttributeDocumenter (Documenter ):
@@ -961,26 +883,12 @@ class AttributeDocumenter(Documenter):
961883 option_spec ['annotation' ] = annotation_option
962884 option_spec ['no-value' ] = bool_option
963885
964- # must be higher than the MethodDocumenter, else it will recognize
965- # some non-data descriptors as methods
966- priority = 10
967-
968886 @staticmethod
969887 def is_function_or_method (obj : Any ) -> bool :
970888 return (
971889 inspect .isfunction (obj ) or inspect .isbuiltin (obj ) or inspect .ismethod (obj )
972890 )
973891
974- @classmethod
975- def can_document_member (
976- cls : type [Documenter ], member : Any , membername : str , isattr : bool , parent : Any
977- ) -> bool :
978- if isinstance (parent , ModuleDocumenter ):
979- return False
980- if inspect .isattributedescriptor (member ):
981- return True
982- return not inspect .isroutine (member ) and not isinstance (member , type )
983-
984892
985893class PropertyDocumenter (Documenter ):
986894 """Specialized Documenter subclass for properties."""
@@ -990,26 +898,6 @@ class PropertyDocumenter(Documenter):
990898 objtype = 'property'
991899 member_order = 60
992900
993- # before AttributeDocumenter
994- priority = AttributeDocumenter .priority + 1
995-
996- @classmethod
997- def can_document_member (
998- cls : type [Documenter ], member : Any , membername : str , isattr : bool , parent : Any
999- ) -> bool :
1000- if isinstance (parent , ClassDocumenter ):
1001- if inspect .isproperty (member ):
1002- return True
1003- else :
1004- # See FakeDirective &c in autosummary, parent might not be a
1005- # 'proper' Documenter.
1006- obj = parent .props ._obj if hasattr (parent , 'props' ) else None
1007- __dict__ = safe_getattr (obj , '__dict__' , {})
1008- obj = __dict__ .get (membername )
1009- return isinstance (obj , classmethod ) and inspect .isproperty (obj .__func__ )
1010- else :
1011- return False
1012-
1013901
1014902class TypeAliasDocumenter (Documenter ):
1015903 """Specialized Documenter subclass for type aliases."""
@@ -1025,12 +913,6 @@ class TypeAliasDocumenter(Documenter):
1025913 'no-value' : bool_option ,
1026914 }
1027915
1028- @classmethod
1029- def can_document_member (
1030- cls : type [Documenter ], member : Any , membername : str , isattr : bool , parent : Any
1031- ) -> bool :
1032- return isinstance (member , AnyTypeAliasType )
1033-
1034916
1035917class DocstringSignatureMixin :
1036918 """Retained for compatibility."""
@@ -1078,3 +960,83 @@ def _document_members(
1078960 real_modname = real_modname ,
1079961 check_module = members_check_module and not is_attr ,
1080962 )
963+
964+
965+ def _best_object_type_for_member (
966+ member : Any ,
967+ member_name : str ,
968+ is_attr : bool ,
969+ parent_documenter : Documenter ,
970+ ) -> str | None :
971+ """Return the best object type that supports documenting *member*."""
972+ filtered = []
973+
974+ # Don't document submodules automatically: 'module' is never returned.
975+
976+ try :
977+ if isinstance (member , type ) and issubclass (member , BaseException ):
978+ # priority must be higher than 'class'
979+ filtered .append ((20 , 'exception' ))
980+ except TypeError as exc :
981+ # It's possible for a member to be considered a type, but fail
982+ # issubclass checks due to not being a class. For example:
983+ # https://github.com/sphinx-doc/sphinx/issues/11654#issuecomment-1696790436
984+ msg = f'Failed to discern if member { member } is a BaseException subclass.'
985+ raise ValueError (msg ) from exc
986+
987+ if isinstance (member , type ) or (is_attr and isinstance (member , (NewType , TypeVar ))):
988+ # priority must be higher than 'function', 'class', and 'attribute'
989+ # as NewType can be an attribute and is a class after Python 3.10.
990+ filtered .append ((15 , 'class' ))
991+
992+ if isinstance (parent_documenter , ClassDocumenter ):
993+ if inspect .isproperty (member ):
994+ # priority must be higher than 'attribute'
995+ filtered .append ((11 , 'property' ))
996+
997+ # Support for class properties. Note: these only work on Python 3.9.
998+ elif hasattr (parent_documenter , 'props' ):
999+ # See FakeDirective &c in autosummary, parent might not be a
1000+ # 'proper' Documenter.
1001+ __dict__ = safe_getattr (parent_documenter .props ._obj , '__dict__' , {})
1002+ obj = __dict__ .get (member_name )
1003+ if isinstance (obj , classmethod ) and inspect .isproperty (obj .__func__ ):
1004+ # priority must be higher than 'attribute'
1005+ filtered .append ((11 , 'property' ))
1006+
1007+ if not isinstance (parent_documenter , ModuleDocumenter ):
1008+ if inspect .isattributedescriptor (member ) or not (
1009+ inspect .isroutine (member ) or isinstance (member , type )
1010+ ):
1011+ # priority must be higher than 'method', else it will recognise
1012+ # some non-data descriptors as methods
1013+ filtered .append ((10 , 'attribute' ))
1014+
1015+ if inspect .isroutine (member ) and not isinstance (
1016+ parent_documenter , ModuleDocumenter
1017+ ):
1018+ # priority must be higher than 'function'
1019+ filtered .append ((1 , 'method' ))
1020+
1021+ if (
1022+ inspect .isfunction (member )
1023+ or inspect .isbuiltin (member )
1024+ or (
1025+ inspect .isroutine (member )
1026+ and isinstance (parent_documenter , ModuleDocumenter )
1027+ )
1028+ ):
1029+ # supports functions, builtins and bound methods exported
1030+ # at the module level
1031+ filtered .extend (((0 , 'function' ), (- 1 , 'decorator' )))
1032+
1033+ if isinstance (member , AnyTypeAliasType ):
1034+ filtered .append ((0 , 'type' ))
1035+
1036+ if isinstance (parent_documenter , ModuleDocumenter ) and is_attr :
1037+ filtered .append ((- 10 , 'data' ))
1038+
1039+ if filtered :
1040+ # return the highest priority object type
1041+ return max (filtered , key = operator .itemgetter (0 ))[1 ]
1042+ return None
0 commit comments