88from sphinx .locale import __
99
1010if TYPE_CHECKING :
11- from typing import Any
11+ from collections .abc import Mapping , Set
12+ from typing import Any , Literal , Self
1213
13- from sphinx .ext .autodoc ._documenters import Documenter
14+ from sphinx .ext .autodoc ._sentinels import ALL_T , EMPTY_T , SUPPRESS_T
15+ from sphinx .util .typing import OptionSpec
1416
1517
1618# common option names for autodoc directives
3941})
4042
4143
44+ class _AutoDocumenterOptions :
45+ # TODO: make immutable.
46+
47+ no_index : Literal [True ] | None = None
48+ no_index_entry : Literal [True ] | None = None
49+
50+ # module-like options
51+ members : ALL_T | list [str ] | None = None
52+ undoc_members : Literal [True ] | None = None
53+ inherited_members : Set [str ] | None = None
54+ show_inheritance : Literal [True ] | None = None
55+ synopsis : str | None = None
56+ platform : str | None = None
57+ deprecated : Literal [True ] | None = None
58+ member_order : Literal ['alphabetical' , 'bysource' , 'groupwise' ] | None = None
59+ exclude_members : EMPTY_T | set [str ] | None = None
60+ private_members : ALL_T | list [str ] | None = None
61+ special_members : ALL_T | list [str ] | None = None
62+ imported_members : Literal [True ] | None = None
63+ ignore_module_all : Literal [True ] | None = None
64+ no_value : Literal [True ] | None = None
65+
66+ # class-like options (class, exception)
67+ class_doc_from : Literal ['both' , 'class' , 'init' ] | None = None
68+
69+ # assignment-like (data, attribute)
70+ annotation : SUPPRESS_T | str | None = None
71+
72+ noindex : Literal [True ] | None = None
73+
74+ def __init__ (self , ** kwargs : Any ) -> None :
75+ vars (self ).update (kwargs )
76+
77+ def __repr__ (self ) -> str :
78+ args = ', ' .join (f'{ k } ={ v !r} ' for k , v in vars (self ).items ())
79+ return f'_AutoDocumenterOptions({ args } )'
80+
81+ def __getattr__ (self , name : str ) -> object :
82+ return None # return None for missing attributes
83+
84+ def copy (self ) -> Self :
85+ return self .__class__ (** vars (self ))
86+
87+ @classmethod
88+ def from_directive_options (cls , opts : Mapping [str , Any ], / ) -> Self :
89+ return cls (** {k .replace ('-' , '_' ): v for k , v in opts .items () if v is not None })
90+
91+ def merge_member_options (self ) -> Self :
92+ """Merge :private-members: and :special-members: into :members:"""
93+ if self .members is ALL :
94+ # merging is not needed when members: ALL
95+ return self
96+
97+ members = self .members or []
98+ for others in self .private_members , self .special_members :
99+ if others is not None and others is not ALL :
100+ members .extend (others )
101+ new = self .copy ()
102+ new .members = list (dict .fromkeys (members )) # deduplicate; preserve order
103+ return new
104+
105+
42106def identity (x : Any ) -> Any :
43107 return x
44108
45109
46- def members_option (arg : Any ) -> object | list [str ]:
110+ def members_option (arg : str | None ) -> ALL_T | list [str ] | None :
47111 """Used to convert the :members: option to auto directives."""
48- if arg in { None , True } :
112+ if arg is None or arg is True :
49113 return ALL
50- elif arg is False :
114+ if arg is False :
51115 return None
52- else :
53- return [x .strip () for x in arg .split (',' ) if x .strip ()]
116+ return [stripped for x in arg .split (',' ) if (stripped := x .strip ())]
54117
55118
56- def exclude_members_option (arg : Any ) -> object | set [str ]:
119+ def exclude_members_option (arg : str | None ) -> EMPTY_T | set [str ]:
57120 """Used to convert the :exclude-members: option."""
58- if arg in { None , True } :
121+ if arg is None or arg is True :
59122 return EMPTY
60- return {x . strip () for x in arg .split (',' ) if x .strip ()}
123+ return {stripped for x in arg .split (',' ) if ( stripped := x .strip () )}
61124
62125
63- def inherited_members_option (arg : Any ) -> set [str ]:
126+ def inherited_members_option (arg : str | None ) -> set [str ]:
64127 """Used to convert the :inherited-members: option to auto directives."""
65- if arg in { None , True } :
128+ if arg is None or arg is True :
66129 return {'object' }
67- elif arg :
130+ if arg :
68131 return {x .strip () for x in arg .split (',' )}
69- else :
70- return set ()
132+ return set ()
71133
72134
73- def member_order_option (arg : Any ) -> str | None :
135+ def member_order_option (
136+ arg : str | None ,
137+ ) -> Literal ['alphabetical' , 'bysource' , 'groupwise' ] | None :
74138 """Used to convert the :member-order: option to auto directives."""
75- if arg in { None , True } :
139+ if arg is None or arg is True :
76140 return None
77- elif arg in {'alphabetical' , 'bysource' , 'groupwise' }:
78- return arg
79- else :
80- raise ValueError (__ ('invalid value for member-order option: %s' ) % arg )
141+ if arg in {'alphabetical' , 'bysource' , 'groupwise' }:
142+ return arg # type: ignore[return-value]
143+ raise ValueError (__ ('invalid value for member-order option: %s' ) % arg )
81144
82145
83- def class_doc_from_option (arg : Any ) -> str | None :
146+ def class_doc_from_option (arg : str | None ) -> Literal [ 'both' , 'class' , 'init' ] :
84147 """Used to convert the :class-doc-from: option to autoclass directives."""
85148 if arg in {'both' , 'class' , 'init' }:
86- return arg
87- else :
88- raise ValueError (__ ('invalid value for class-doc-from option: %s' ) % arg )
149+ return arg # type: ignore[return-value]
150+ raise ValueError (__ ('invalid value for class-doc-from option: %s' ) % arg )
89151
90152
91- def annotation_option (arg : Any ) -> Any :
92- if arg in { None , True } :
153+ def annotation_option (arg : str | None ) -> SUPPRESS_T | str | Literal [ False ] :
154+ if arg is None or arg is True :
93155 # suppress showing the representation of the object
94156 return SUPPRESS
95- else :
96- return arg
157+ return arg
97158
98159
99- def bool_option (arg : Any ) -> bool :
160+ def bool_option (arg : str | None ) -> bool :
100161 """Used to convert flag options to auto directives. (Instead of
101162 directives.flag(), which returns None).
102163 """
@@ -137,14 +198,14 @@ def __getattr__(self, name: str) -> Any:
137198
138199
139200def _process_documenter_options (
140- documenter : type [Documenter ],
141201 * ,
202+ option_spec : OptionSpec ,
142203 default_options : dict [str , str | bool ],
143- options : dict [str , str ],
144- ) -> Options :
204+ options : dict [str , str | None ],
205+ ) -> dict [ str , object ] :
145206 """Recognize options of Documenter from user input."""
146207 for name in AUTODOC_DEFAULT_OPTIONS :
147- if name not in documenter . option_spec :
208+ if name not in option_spec :
148209 continue
149210
150211 negated = options .pop (f'no-{ name } ' , True ) is None
@@ -153,13 +214,13 @@ def _process_documenter_options(
153214 # take value from options if present or extend it
154215 # with autodoc_default_options if necessary
155216 if name in AUTODOC_EXTENDABLE_OPTIONS :
156- if options [name ] is not None and options [name ].startswith ('+' ):
157- options [name ] = f'{ default_options [name ]} ,{ options [name ][1 :]} '
217+ opt_value = options [name ]
218+ if opt_value is not None and opt_value .startswith ('+' ):
219+ options [name ] = f'{ default_options [name ]} ,{ opt_value [1 :]} '
158220 else :
159221 options [name ] = default_options [name ] # type: ignore[assignment]
160- elif options .get (name ) is not None :
222+ elif ( opt_value := options .get (name ) ) is not None :
161223 # remove '+' from option argument if there's nothing to merge it with
162- options [name ] = options [ name ] .removeprefix ('+' )
224+ options [name ] = opt_value .removeprefix ('+' )
163225
164- opts = assemble_option_dict (options .items (), documenter .option_spec )
165- return Options (opts )
226+ return assemble_option_dict (options .items (), option_spec ) # type: ignore[arg-type]
0 commit comments