Skip to content

Commit 79c8d9c

Browse files
committed
Merge branch 'cleanup/util-2c-typing-reformat-style' into cleanup/util-2d-inspect-typeguards
2 parents bf7c3a4 + ed93af8 commit 79c8d9c

File tree

2 files changed

+65
-36
lines changed

2 files changed

+65
-36
lines changed

sphinx/ext/autodoc/mock.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def __contains__(self, key: str) -> bool:
5454
def __iter__(self) -> Iterator[Any]:
5555
return iter([])
5656

57-
def __mro_entries__(self, bases: tuple[Any, ...]) -> tuple[type[Any], ...]:
57+
def __mro_entries__(self, bases: tuple[Any, ...]) -> tuple[type, ...]:
5858
return (self.__class__,)
5959

6060
def __getitem__(self, key: Any) -> _MockObject:
@@ -160,8 +160,8 @@ def mock(modnames: list[str]) -> Iterator[None]:
160160
# mock modules are enabled here
161161
...
162162
"""
163+
finder = MockFinder(modnames)
163164
try:
164-
finder = MockFinder(modnames)
165165
sys.meta_path.insert(0, finder)
166166
yield
167167
finally:

sphinx/util/typing.py

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424

2525
if TYPE_CHECKING:
2626
from collections.abc import Mapping
27-
from typing import Final, Protocol
27+
from collections.abc import Set as AbstractSet
28+
from typing import Final, Literal, Protocol
2829

2930
from typing_extensions import TypeGuard
3031

@@ -33,6 +34,9 @@
3334
class _SpecialFormInterface(Protocol):
3435
_name: str
3536

37+
_RestifyMode = Literal['fully-qualified-except-typing', 'smart']
38+
_StringifyMode = Literal['fully-qualified-except-typing', 'fully-qualified', 'smart']
39+
_MT_co = TypeVar('_MT_co', bound=Any, covariant=True)
3640

3741
if sys.version_info >= (3, 10):
3842
from types import UnionType
@@ -186,7 +190,7 @@ def _get_typing_internal_name(obj: Any) -> str | None:
186190
return getattr(obj, '_name', None)
187191

188192

189-
def restify(cls: Any, mode: str = 'fully-qualified-except-typing') -> str:
193+
def restify(cls: Any, mode: object = 'fully-qualified-except-typing') -> str:
190194
"""Convert a python type-like object to a reST reference.
191195
192196
:param mode: Specify a method how annotations will be stringified.
@@ -200,10 +204,10 @@ def restify(cls: Any, mode: str = 'fully-qualified-except-typing') -> str:
200204
from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading
201205
from sphinx.util import inspect # lazy loading
202206

203-
if mode == 'smart':
204-
modprefix = '~'
205-
else:
206-
modprefix = ''
207+
mode = _validate_restify_mode(mode)
208+
# With an if-else block, mypy infers 'mode' to be a 'str'
209+
# instead of a literal string (and we don't want to cast).
210+
module_prefix = '~' if mode == 'smart' else ''
207211

208212
try:
209213
if cls is None or cls is NoneType:
@@ -213,15 +217,18 @@ def restify(cls: Any, mode: str = 'fully-qualified-except-typing') -> str:
213217
elif isinstance(cls, str):
214218
return cls
215219
elif ismockmodule(cls):
216-
return f':py:class:`{modprefix}{cls.__name__}`'
220+
return f':py:class:`{module_prefix}{cls.__name__}`'
217221
elif ismock(cls):
218-
return f':py:class:`{modprefix}{cls.__module__}.{cls.__name__}`'
219-
elif is_invalid_builtin_class(cls): # this never raises TypeError
220-
return f':py:class:`{modprefix}{_INVALID_BUILTIN_CLASSES[cls]}`'
222+
return f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
223+
elif is_invalid_builtin_class(cls):
224+
# The above predicate never raises TypeError but should not be
225+
# evaluated before determining whether *cls* is a mocked object
226+
# or not; instead of two try-except blocks, we keep it here.
227+
return f':py:class:`{module_prefix}{_INVALID_BUILTIN_CLASSES[cls]}`'
221228
elif inspect.isNewType(cls):
222229
if sys.version_info[:2] >= (3, 10):
223230
# newtypes have correct module info since Python 3.10+
224-
return f':py:class:`{modprefix}{cls.__module__}.{cls.__name__}`'
231+
return f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
225232
return f':py:class:`{cls.__name__}`'
226233
elif UnionType and isinstance(cls, UnionType):
227234
# Union types (PEP 585) retain their definition order when they
@@ -264,7 +271,7 @@ def restify(cls: Any, mode: str = 'fully-qualified-except-typing') -> str:
264271
if _is_annotated_form(__origin__):
265272
text = restify(__origin__, mode)
266273
elif internal_class_name := _get_typing_internal_name(cls):
267-
prefix = '~' if cls.__module__ == 'typing' else modprefix
274+
prefix = '~' if cls.__module__ == 'typing' else module_prefix
268275
text = f':py:class:`{prefix}{cls.__module__}.{internal_class_name}`'
269276
else:
270277
text = restify(__origin__, mode)
@@ -290,13 +297,13 @@ def restify(cls: Any, mode: str = 'fully-qualified-except-typing') -> str:
290297
# handle bpo-46998
291298
return f':py:obj:`~{cls.__module__}.{cls.__name__}`'
292299
elif hasattr(cls, '__qualname__'):
293-
prefix = '~' if cls.__module__ == 'typing' else modprefix
300+
prefix = '~' if cls.__module__ == 'typing' else module_prefix
294301
return f':py:class:`{prefix}{cls.__module__}.{cls.__qualname__}`'
295302
elif isinstance(cls, ForwardRef):
296303
return f':py:class:`{cls.__forward_arg__}`'
297304
else:
298305
# not a class (ex. TypeVar)
299-
prefix = '~' if cls.__module__ == 'typing' else modprefix
306+
prefix = '~' if cls.__module__ == 'typing' else module_prefix
300307
return f':py:obj:`{prefix}{cls.__module__}.{cls.__name__}`'
301308
except (AttributeError, TypeError):
302309
return inspect.object_description(cls)
@@ -323,16 +330,10 @@ def stringify_annotation(
323330
from sphinx.ext.autodoc.mock import ismock, ismockmodule # lazy loading
324331
from sphinx.util.inspect import isNewType # lazy loading
325332

326-
allowed_modes = {'fully-qualified-except-typing', 'fully-qualified', 'smart'}
327-
if mode not in allowed_modes:
328-
allowed = ', '.join(map(repr, sorted(allowed_modes)))
329-
msg = f'%r must be one of %s; got {mode!r}' % ('mode', allowed)
330-
raise ValueError(msg)
331-
332-
if mode == 'smart':
333-
module_prefix = '~'
334-
else:
335-
module_prefix = ''
333+
mode = _validate_stringify_mode(mode)
334+
# With an if-else block, mypy infers 'mode' to be a 'str'
335+
# instead of a literal string (and we don't want to cast).
336+
module_prefix = '~' if mode == 'smart' else ''
336337

337338
# The values below must be strings if the objects are well-formed.
338339
annotation_qualname: str = getattr(annotation, '__qualname__', '')
@@ -440,29 +441,57 @@ def stringify_annotation(
440441
return f'{module_prefix}{qualname}'
441442

442443

443-
def _stringify_literal_arg(arg: Any, mode: str) -> str:
444+
def _restify_literal_arg(arg: Any, mode: _RestifyMode) -> str:
444445
from sphinx.util.inspect import isenumattribute # lazy loading
445446

446447
if isenumattribute(arg):
447-
enumcls = arg.__class__
448-
if mode == 'smart':
449-
# MyEnum.member
450-
return f'{enumcls.__qualname__}.{arg.name}'
451-
# module.MyEnum.member
452-
return f'{enumcls.__module__}.{enumcls.__qualname__}.{arg.name}'
448+
enum_cls = arg.__class__
449+
prefix = '~' if mode == 'smart' or enum_cls.__module__ == 'typing' else ''
450+
return f':py:attr:`{prefix}{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}`'
453451
return repr(arg)
454452

455453

456-
def _restify_literal_arg(arg: Any, mode: str) -> str:
454+
def _stringify_literal_arg(arg: Any, mode: _StringifyMode) -> str:
457455
from sphinx.util.inspect import isenumattribute # lazy loading
458456

459457
if isenumattribute(arg):
460458
enum_cls = arg.__class__
461-
prefix = '~' if mode == 'smart' or enum_cls.__module__ == 'typing' else ''
462-
return f':py:attr:`{prefix}{enum_cls.__module__}.{enum_cls.__qualname__}.{arg.name}`'
459+
# 'MyEnum.member' in 'smart' mode, otherwise 'module.MyEnum.member'
460+
prefix = '' if mode == 'smart' else enum_cls.__module__
461+
return f'{prefix}.{enum_cls.__qualname__}.{arg.name}'
463462
return repr(arg)
464463

465464

465+
# validation functions (keep them as separate functions to handle enumerations
466+
# values instead of plain strings in the future and to be able to add better
467+
# type annotations using literal values)
468+
def _validate_mode(mode: Any, allowed_modes: AbstractSet[_MT_co]) -> _MT_co:
469+
if mode not in allowed_modes:
470+
allowed = ', '.join(map(repr, sorted(allowed_modes)))
471+
msg = f'%r must be one of %s; got {mode!r}' % ('mode', allowed)
472+
raise ValueError(msg)
473+
return mode
474+
475+
476+
def _validate_restify_mode(mode: Any) -> _RestifyMode:
477+
# add more allowed modes here (e.g., enumeration values)
478+
allowed_modes: AbstractSet[_RestifyMode] = {
479+
'fully-qualified-except-typing',
480+
'smart',
481+
}
482+
return _validate_mode(mode, allowed_modes)
483+
484+
485+
def _validate_stringify_mode(mode: Any) -> _StringifyMode:
486+
# add more allowed modes here (e.g., enumeration values)
487+
allowed_modes: AbstractSet[_StringifyMode] = {
488+
'fully-qualified-except-typing',
489+
'fully-qualified',
490+
'smart',
491+
}
492+
return _validate_mode(mode, allowed_modes)
493+
494+
466495
# deprecated name -> (object to return, canonical path or empty string, removal version)
467496
_DEPRECATED_OBJECTS: dict[str, tuple[Any, str, tuple[int, int]]] = {
468497
'stringify': (stringify_annotation, 'sphinx.util.typing.stringify_annotation', (8, 0)),

0 commit comments

Comments
 (0)