Skip to content

Commit 4390b7f

Browse files
committed
cleanup
1 parent 1436236 commit 4390b7f

File tree

2 files changed

+87
-93
lines changed

2 files changed

+87
-93
lines changed

.ruff.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -554,7 +554,6 @@ exclude = [
554554
"sphinx/util/http_date.py",
555555
"sphinx/util/matching.py",
556556
"sphinx/util/index_entries.py",
557-
"sphinx/util/typing.py",
558557
"sphinx/util/images.py",
559558
"sphinx/util/exceptions.py",
560559
"sphinx/util/requests.py",

sphinx/util/typing.py

Lines changed: 87 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
from docutils.parsers.rst.states import Inliner
1515

1616
if TYPE_CHECKING:
17-
import enum
17+
from collections.abc import Mapping
18+
from typing import Final
1819

1920
from sphinx.application import Sphinx
2021

@@ -24,7 +25,7 @@
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:
7172
PathMatcher = 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
7881
OptionSpec = dict[str, Callable[[str], Any]]
@@ -115,7 +118,9 @@ class ExtensionMetadata(TypedDict, total=False):
115118

116119

117120
def 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

Comments
 (0)