Skip to content

Commit 159c267

Browse files
dstansbyAA-Turner
andauthored
Display typing.Annotated metadata in the Python domain (#11785)
Co-authored-by: Adam Turner <[email protected]>
1 parent 02c265c commit 159c267

File tree

3 files changed

+34
-10
lines changed

3 files changed

+34
-10
lines changed

CHANGES.rst

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ Features added
7171
parses the provided text into inline elements and text nodes.
7272

7373
Patch by Adam Turner.
74-
7574
* #12258: Support ``typing_extensions.Unpack``
7675
Patch by Bénédikt Tran and Adam Turner.
7776
* #12524: Add a ``class`` option to the :rst:dir:`toctree` directive.
@@ -100,6 +99,9 @@ Features added
10099
* #12508: LaTeX: Revamped styling of all admonitions, with addition of a
101100
title row with icon.
102101
Patch by Jean-François B.
102+
* #11773: Display :py:class:`~typing.Annotated` annotations
103+
with their metadata in the Python domain.
104+
Patch by Adam Turner and David Stansby.
103105

104106
Bugs fixed
105107
----------

sphinx/util/typing.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,14 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
261261
# evaluated before determining whether *cls* is a mocked object
262262
# or not; instead of two try-except blocks, we keep it here.
263263
return f':py:class:`{module_prefix}{_INVALID_BUILTIN_CLASSES[cls]}`'
264+
elif _is_annotated_form(cls):
265+
args = restify(cls.__args__[0], mode)
266+
meta = ', '.join(map(repr, cls.__metadata__))
267+
if sys.version_info[:2] <= (3, 11):
268+
# Hardcoded to fix errors on Python 3.11 and earlier.
269+
return fr':py:class:`~typing.Annotated`\ [{args}, {meta}]'
270+
return (f':py:class:`{module_prefix}{cls.__module__}.{cls.__name__}`'
271+
fr'\ [{args}, {meta}]')
264272
elif inspect.isNewType(cls):
265273
if sys.version_info[:2] >= (3, 10):
266274
# newtypes have correct module info since Python 3.10+
@@ -497,7 +505,14 @@ def stringify_annotation(
497505
for a in annotation_args)
498506
return f'{module_prefix}Literal[{args}]'
499507
elif _is_annotated_form(annotation): # for py39+
500-
return stringify_annotation(annotation_args[0], mode)
508+
args = stringify_annotation(annotation_args[0], mode)
509+
meta = ', '.join(map(repr, annotation.__metadata__))
510+
if sys.version_info[:2] <= (3, 11):
511+
if mode == 'fully-qualified-except-typing':
512+
return f'Annotated[{args}, {meta}]'
513+
module_prefix = module_prefix.replace('builtins', 'typing')
514+
return f'{module_prefix}Annotated[{args}, {meta}]'
515+
return f'{module_prefix}Annotated[{args}, {meta}]'
501516
elif all(is_system_TypeVar(a) for a in annotation_args):
502517
# Suppress arguments if all system defined TypeVars (ex. Dict[KT, VT])
503518
return module_prefix + qualname

tests/test_util/test_util_typing.py

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Tests util.typing functions."""
22

3+
import dataclasses
34
import sys
45
import typing as t
56
from collections import abc
@@ -73,6 +74,11 @@ class BrokenType:
7374
__args__ = int
7475

7576

77+
@dataclasses.dataclass(frozen=True)
78+
class Gt:
79+
gt: float
80+
81+
7682
def test_restify():
7783
assert restify(int) == ":py:class:`int`"
7884
assert restify(int, "smart") == ":py:class:`int`"
@@ -187,10 +193,11 @@ def test_restify_type_hints_containers():
187193
"[:py:obj:`None`]")
188194

189195

190-
@pytest.mark.xfail(sys.version_info[:2] <= (3, 11), reason='Needs fixing.')
191196
def test_restify_Annotated():
192-
assert restify(Annotated[str, "foo", "bar"]) == ':py:class:`~typing.Annotated`\\ [:py:class:`str`]'
193-
assert restify(Annotated[str, "foo", "bar"], 'smart') == ':py:class:`~typing.Annotated`\\ [:py:class:`str`]'
197+
assert restify(Annotated[str, "foo", "bar"]) == ":py:class:`~typing.Annotated`\\ [:py:class:`str`, 'foo', 'bar']"
198+
assert restify(Annotated[str, "foo", "bar"], 'smart') == ":py:class:`~typing.Annotated`\\ [:py:class:`str`, 'foo', 'bar']"
199+
assert restify(Annotated[float, Gt(-10.0)]) == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, Gt(gt=-10.0)]'
200+
assert restify(Annotated[float, Gt(-10.0)], 'smart') == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, Gt(gt=-10.0)]'
194201

195202

196203
def test_restify_type_hints_Callable():
@@ -499,9 +506,12 @@ def test_stringify_type_hints_pep_585():
499506
assert stringify_annotation(tuple[List[dict[int, str]], str, ...], "smart") == "tuple[~typing.List[dict[int, str]], str, ...]"
500507

501508

509+
@pytest.mark.xfail(sys.version_info[:2] <= (3, 9), reason='Needs fixing.')
502510
def test_stringify_Annotated():
503-
assert stringify_annotation(Annotated[str, "foo", "bar"], 'fully-qualified-except-typing') == "str"
504-
assert stringify_annotation(Annotated[str, "foo", "bar"], "smart") == "str"
511+
assert stringify_annotation(Annotated[str, "foo", "bar"], 'fully-qualified-except-typing') == "Annotated[str, 'foo', 'bar']"
512+
assert stringify_annotation(Annotated[str, "foo", "bar"], 'smart') == "~typing.Annotated[str, 'foo', 'bar']"
513+
assert stringify_annotation(Annotated[float, Gt(-10.0)], 'fully-qualified-except-typing') == "Annotated[float, Gt(gt=-10.0)]"
514+
assert stringify_annotation(Annotated[float, Gt(-10.0)], 'smart') == "~typing.Annotated[float, Gt(gt=-10.0)]"
505515

506516

507517
def test_stringify_Unpack():
@@ -662,7 +672,6 @@ def test_stringify_type_hints_alias():
662672

663673

664674
def test_stringify_type_Literal():
665-
from typing import Literal # type: ignore[attr-defined]
666675
assert stringify_annotation(Literal[1, "2", "\r"], 'fully-qualified-except-typing') == "Literal[1, '2', '\\r']"
667676
assert stringify_annotation(Literal[1, "2", "\r"], "fully-qualified") == "typing.Literal[1, '2', '\\r']"
668677
assert stringify_annotation(Literal[1, "2", "\r"], "smart") == "~typing.Literal[1, '2', '\\r']"
@@ -704,8 +713,6 @@ def test_stringify_mock():
704713

705714

706715
def test_stringify_type_ForwardRef():
707-
from typing import ForwardRef # type: ignore[attr-defined]
708-
709716
assert stringify_annotation(ForwardRef("MyInt")) == "MyInt"
710717
assert stringify_annotation(ForwardRef("MyInt"), 'smart') == "MyInt"
711718

0 commit comments

Comments
 (0)