1414from docutils .parsers .rst .states import Inliner
1515
1616if TYPE_CHECKING :
17- import enum
17+ from collections .abc import Mapping
18+ from typing import Final
1819
1920 from sphinx .application import Sphinx
2021
2425 UnionType = None
2526
2627# classes that have an incorrect .__module__ attribute
27- _INVALID_BUILTIN_CLASSES = {
28+ _INVALID_BUILTIN_CLASSES : Final [ Mapping [ Any , str ]] = {
2829 Context : 'contextvars.Context' , # Context.__module__ == '_contextvars'
2930 ContextVar : 'contextvars.ContextVar' , # ContextVar.__module__ == '_contextvars'
3031 Token : 'contextvars.Token' , # Token.__module__ == '_contextvars'
@@ -71,8 +72,10 @@ def is_invalid_builtin_class(obj: Any) -> bool:
7172PathMatcher = Callable [[str ], bool ]
7273
7374# common role functions
74- RoleFunction = Callable [[str , str , str , int , Inliner , dict [str , Any ], Sequence [str ]],
75- tuple [list [nodes .Node ], list [nodes .system_message ]]]
75+ RoleFunction = Callable [
76+ [str , str , str , int , Inliner , dict [str , Any ], Sequence [str ]],
77+ tuple [list [nodes .Node ], list [nodes .system_message ]],
78+ ]
7679
7780# A option spec for directive
7881OptionSpec = dict [str , Callable [[str ], Any ]]
@@ -115,7 +118,9 @@ class ExtensionMetadata(TypedDict, total=False):
115118
116119
117120def get_type_hints (
118- obj : Any , globalns : dict [str , Any ] | None = None , localns : dict [str , Any ] | None = None ,
121+ obj : Any ,
122+ globalns : dict [str , Any ] | None = None ,
123+ localns : dict [str , Any ] | None = None ,
119124) -> dict [str , Any ]:
120125 """Return a dictionary containing type hints for a function, method, module or class
121126 object.
@@ -177,32 +182,33 @@ def restify(cls: type | None, mode: str = 'fully-qualified-except-typing') -> st
177182 return f':py:class:`{ modprefix } { cls .__name__ } `'
178183 elif ismock (cls ):
179184 return f':py:class:`{ modprefix } { cls .__module__ } .{ cls .__name__ } `'
180- elif is_invalid_builtin_class (cls ):
185+ elif is_invalid_builtin_class (cls ): # this never raises TypeError
181186 return f':py:class:`{ modprefix } { _INVALID_BUILTIN_CLASSES [cls ]} `'
182187 elif inspect .isNewType (cls ):
183188 if sys .version_info [:2 ] >= (3 , 10 ):
184189 # newtypes have correct module info since Python 3.10+
185190 return f':py:class:`{ modprefix } { cls .__module__ } .{ cls .__name__ } `'
186- else :
187- return ':py:class:`%s`' % cls .__name__
191+ return f':py:class:`{ cls .__name__ } `'
188192 elif UnionType and isinstance (cls , UnionType ):
189193 # Union types (PEP 585) retain their definition order when they
190194 # are printed natively and ``None``-like types are kept as is.
191195 return ' | ' .join (restify (a , mode ) for a in cls .__args__ )
192196 elif cls .__module__ in ('__builtin__' , 'builtins' ):
193197 if hasattr (cls , '__args__' ):
194- if not cls .__args__ : # Empty tuple, list, ...
195- return fr':py:class:`{ cls .__name__ } `\ [{ cls .__args__ !r} ]'
196-
197- concatenated_args = ', ' .join (restify (arg , mode ) for arg in cls .__args__ )
198- return fr':py:class:`{ cls .__name__ } `\ [{ concatenated_args } ]'
199- else :
200- return ':py:class:`%s`' % cls .__name__
201- elif (inspect .isgenericalias (cls )
202- and cls .__module__ == 'typing'
203- and cls .__origin__ is Union ): # type: ignore[attr-defined]
198+ __args__ : tuple [Any , ...] = cls .__args__
199+ if not __args__ : # Empty tuple, list, ...
200+ return rf':py:class:`{ cls .__name__ } `\ [{ __args__ !r} ]'
201+
202+ concatenated_args = ', ' .join (restify (arg , mode ) for arg in __args__ )
203+ return rf':py:class:`{ cls .__name__ } `\ [{ concatenated_args } ]'
204+ return f':py:class:`{ cls .__name__ } `'
205+ elif (
206+ inspect .isgenericalias (cls )
207+ and cls .__module__ == 'typing'
208+ and cls .__origin__ is Union # type: ignore[attr-defined]
209+ ):
204210 # *cls* is defined in ``typing``, and thus ``__args__`` must exist;
205- if NoneType in (__args__ := cls .__args__ ):
211+ if NoneType in (__args__ := cls .__args__ ): # type: ignore[attr-defined]
206212 # Shape: Union[T_1, ..., T_k, None, T_{k+1}, ..., T_n]
207213 #
208214 # Note that we keep Literal[None] in their rightful place
@@ -237,41 +243,29 @@ def restify(cls: type | None, mode: str = 'fully-qualified-except-typing') -> st
237243 elif all (is_system_TypeVar (a ) for a in cls .__args__ ):
238244 # Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
239245 pass
240- elif (cls .__module__ == 'typing'
241- and cls ._name == 'Callable' ): # type: ignore[attr-defined]
246+ elif cls .__module__ == 'typing' and cls ._name == 'Callable' : # type: ignore[attr-defined]
242247 args = ', ' .join (restify (a , mode ) for a in cls .__args__ [:- 1 ])
243- text += fr" \ [[{ args } ], { restify (cls .__args__ [- 1 ], mode )} ]"
248+ text += rf' \ [[{ args } ], { restify (cls .__args__ [- 1 ], mode )} ]'
244249 elif cls .__module__ == 'typing' and getattr (origin , '_name' , None ) == 'Literal' :
245- literal_args = []
246- for a in cls .__args__ :
247- if inspect .isenumattribute (a ):
248- literal_args .append (_format_literal_enum_arg (a , mode = mode ))
249- else :
250- literal_args .append (repr (a ))
251- text += r"\ [%s]" % ', ' .join (literal_args )
252- del literal_args
250+ literals = ', ' .join (_restify_literal_arg (a , mode ) for a in cls .__args__ )
251+ text += rf'\ [{ literals } ]'
253252 elif cls .__args__ :
254- text += r"\ [%s]" % ", " .join (restify (a , mode ) for a in cls .__args__ )
255-
253+ text += rf'\ [{ ", " .join (restify (a , mode ) for a in cls .__args__ )} ]'
256254 return text
257255 elif isinstance (cls , typing ._SpecialForm ):
258256 return f':py:obj:`~{ cls .__module__ } .{ cls ._name } `'
259257 elif sys .version_info [:2 ] >= (3 , 11 ) and cls is typing .Any :
260258 # handle bpo-46998
261259 return f':py:obj:`~{ cls .__module__ } .{ cls .__name__ } `'
262260 elif hasattr (cls , '__qualname__' ):
263- if cls .__module__ == 'typing' :
264- return f':py:class:`~{ cls .__module__ } .{ cls .__qualname__ } `'
265- else :
266- return f':py:class:`{ modprefix } { cls .__module__ } .{ cls .__qualname__ } `'
261+ prefix = '~' if cls .__module__ == 'typing' else modprefix
262+ return f':py:class:`{ prefix } { cls .__module__ } .{ cls .__qualname__ } `'
267263 elif isinstance (cls , ForwardRef ):
268- return ':py:class:`%s`' % cls .__forward_arg__
264+ return f ':py:class:`{ cls .__forward_arg__ } `'
269265 else :
270266 # not a class (ex. TypeVar)
271- if cls .__module__ == 'typing' :
272- return f':py:obj:`~{ cls .__module__ } .{ cls .__name__ } `'
273- else :
274- return f':py:obj:`{ modprefix } { cls .__module__ } .{ cls .__name__ } `'
267+ prefix = '~' if cls .__module__ == 'typing' else modprefix
268+ return f':py:obj:`{ prefix } { cls .__module__ } .{ cls .__name__ } `'
275269 except (AttributeError , TypeError ):
276270 return inspect .object_description (cls )
277271
@@ -297,67 +291,66 @@ def stringify_annotation(
297291 from sphinx .ext .autodoc .mock import ismock , ismockmodule # lazy loading
298292 from sphinx .util .inspect import isNewType # lazy loading
299293
300- if mode not in {'fully-qualified-except-typing' , 'fully-qualified' , 'smart' }:
301- msg = ("'mode' must be one of 'fully-qualified-except-typing', "
302- f"'fully-qualified', or 'smart'; got { mode !r} ." )
294+ allowed_modes = {'fully-qualified-except-typing' , 'fully-qualified' , 'smart' }
295+ if mode not in allowed_modes :
296+ allowed = ', ' .join (map (repr , sorted (allowed_modes )))
297+ msg = f'%r must be one of %s; got { mode !r} ' % ('mode' , allowed )
303298 raise ValueError (msg )
304299
305300 if mode == 'smart' :
306301 module_prefix = '~'
307302 else :
308303 module_prefix = ''
309304
310- annotation_qualname = getattr (annotation , '__qualname__' , '' )
311- annotation_module = getattr (annotation , '__module__' , '' )
312- annotation_name = getattr (annotation , '__name__' , '' )
305+ # The values below must be strings if the objects are well-formed.
306+ annotation_qualname : str = getattr (annotation , '__qualname__' , '' )
307+ annotation_module : str = getattr (annotation , '__module__' , '' )
308+ annotation_name : str = getattr (annotation , '__name__' , '' )
313309 annotation_module_is_typing = annotation_module == 'typing'
314310
315311 if isinstance (annotation , str ):
316312 if annotation .startswith ("'" ) and annotation .endswith ("'" ):
317313 # might be a double Forward-ref'ed type. Go unquoting.
318314 return annotation [1 :- 1 ]
319- else :
320- return annotation
315+ return annotation
321316 elif isinstance (annotation , TypeVar ):
322317 if annotation_module_is_typing and mode in {'fully-qualified-except-typing' , 'smart' }:
323318 return annotation_name
324- else :
325- return module_prefix + f'{ annotation_module } .{ annotation_name } '
319+ return f'{ module_prefix } { annotation_module } .{ annotation_name } '
326320 elif isNewType (annotation ):
327321 if sys .version_info [:2 ] >= (3 , 10 ):
328322 # newtypes have correct module info since Python 3.10+
329- return module_prefix + f'{ annotation_module } .{ annotation_name } '
330- else :
331- return annotation_name
323+ return f'{ module_prefix } { annotation_module } .{ annotation_name } '
324+ return annotation_name
332325 elif not annotation :
333326 return repr (annotation )
334327 elif annotation is NoneType :
335328 return 'None'
336329 elif ismockmodule (annotation ):
337- return module_prefix + annotation_name
330+ return f' { module_prefix } { annotation_name } '
338331 elif ismock (annotation ):
339- return module_prefix + f' { annotation_module } .{ annotation_name } '
332+ return f' { module_prefix } { annotation_module } .{ annotation_name } '
340333 elif is_invalid_builtin_class (annotation ):
341- return module_prefix + _INVALID_BUILTIN_CLASSES [annotation ]
342- elif str (annotation ).startswith ('typing.Annotated' ): # for py310 +
334+ return f' { module_prefix } { _INVALID_BUILTIN_CLASSES [annotation ]} '
335+ elif str (annotation ).startswith ('typing.Annotated' ): # for py39 +
343336 pass
344337 elif annotation_module == 'builtins' and annotation_qualname :
345- if (args := getattr (annotation , '__args__' , None )) is not None : # PEP 585 generic
346- if not args : # Empty tuple, list, ...
347- return repr (annotation )
348-
349- concatenated_args = ', ' .join (stringify_annotation (arg , mode ) for arg in args )
350- return f'{ annotation_qualname } [{ concatenated_args } ]'
351- else :
338+ if (args := getattr (annotation , '__args__' , None )) is None :
352339 return annotation_qualname
340+
341+ # PEP 585 generic
342+ if not args : # Empty tuple, list, ...
343+ return repr (annotation )
344+ concatenated_args = ', ' .join (stringify_annotation (arg , mode ) for arg in args )
345+ return f'{ annotation_qualname } [{ concatenated_args } ]'
353346 elif annotation is Ellipsis :
354347 return '...'
355348
356349 module_prefix = f'{ annotation_module } .'
357- annotation_forward_arg = getattr (annotation , '__forward_arg__' , None )
350+ annotation_forward_arg : str | None = getattr (annotation , '__forward_arg__' , None )
358351 if annotation_qualname or (annotation_module_is_typing and not annotation_forward_arg ):
359352 if mode == 'smart' :
360- module_prefix = '~' + module_prefix
353+ module_prefix = f'~ { module_prefix } '
361354 if annotation_module_is_typing and mode == 'fully-qualified-except-typing' :
362355 module_prefix = ''
363356 else :
@@ -375,7 +368,8 @@ def stringify_annotation(
375368 qualname = annotation_qualname
376369 else :
377370 qualname = stringify_annotation (
378- annotation .__origin__ , 'fully-qualified-except-typing' ,
371+ annotation .__origin__ ,
372+ 'fully-qualified-except-typing' ,
379373 ).replace ('typing.' , '' ) # ex. Union
380374 elif annotation_qualname :
381375 qualname = annotation_qualname
@@ -401,40 +395,41 @@ def stringify_annotation(
401395 returns = stringify_annotation (annotation_args [- 1 ], mode )
402396 return f'{ module_prefix } Callable[[{ args } ], { returns } ]'
403397 elif qualname == 'Literal' :
404- from sphinx .util .inspect import isenumattribute # lazy loading
405-
406- def format_literal_arg (arg : Any ) -> str :
407- if isenumattribute (arg ):
408- enumcls = arg .__class__
409-
410- if mode == 'smart' :
411- # MyEnum.member
412- return f'{ enumcls .__qualname__ } .{ arg .name } '
413-
414- # module.MyEnum.member
415- return f'{ enumcls .__module__ } .{ enumcls .__qualname__ } .{ arg .name } '
416- return repr (arg )
417-
418- args = ', ' .join (map (format_literal_arg , annotation_args ))
419- return f'{ module_prefix } Literal[{ args } ]'
398+ literals = ', ' .join (_stringify_literal_arg (a , mode ) for a in annotation_args )
399+ return f'{ module_prefix } Literal[{ literals } ]'
420400 elif str (annotation ).startswith ('typing.Annotated' ): # for py39+
421401 return stringify_annotation (annotation_args [0 ], mode )
422402 elif all (is_system_TypeVar (a ) for a in annotation_args ):
423403 # Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
424- return module_prefix + qualname
404+ return f' { module_prefix } { qualname } '
425405 else :
426406 args = ', ' .join (stringify_annotation (a , mode ) for a in annotation_args )
427407 return f'{ module_prefix } { qualname } [{ args } ]'
428408
429- return module_prefix + qualname
409+ return f' { module_prefix } { qualname } '
430410
431411
432- def _format_literal_enum_arg (arg : enum .Enum , / , * , mode : str ) -> str :
433- enum_cls = arg .__class__
434- if mode == 'smart' or enum_cls .__module__ == 'typing' :
435- return f':py:attr:`~{ enum_cls .__module__ } .{ enum_cls .__qualname__ } .{ arg .name } `'
436- else :
437- return f':py:attr:`{ enum_cls .__module__ } .{ enum_cls .__qualname__ } .{ arg .name } `'
412+ def _stringify_literal_arg (arg : Any , mode : str ) -> str :
413+ from sphinx .util .inspect import isenumattribute # lazy loading
414+
415+ if isenumattribute (arg ):
416+ enumcls = arg .__class__
417+ if mode == 'smart' :
418+ # MyEnum.member
419+ return f'{ enumcls .__qualname__ } .{ arg .name } '
420+ # module.MyEnum.member
421+ return f'{ enumcls .__module__ } .{ enumcls .__qualname__ } .{ arg .name } '
422+ return repr (arg )
423+
424+
425+ def _restify_literal_arg (arg : Any , mode : str ) -> str :
426+ from sphinx .util .inspect import isenumattribute # lazy loading
427+
428+ if isenumattribute (arg ):
429+ enum_cls = arg .__class__
430+ prefix = '~' if mode == 'smart' or enum_cls .__module__ == 'typing' else ''
431+ return f':py:attr:`{ prefix } { enum_cls .__module__ } .{ enum_cls .__qualname__ } .{ arg .name } `'
432+ return repr (arg )
438433
439434
440435# deprecated name -> (object to return, canonical path or empty string, removal version)
0 commit comments