From 197a3dd53e6b2dd503e1957630c442fc60c4b03e Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 18 Jul 2024 12:37:35 +0100 Subject: [PATCH 1/8] Support callables in Annotated --- sphinx/domains/python/_annotations.py | 24 +++++++++++++++- tests/test_domains/test_domain_py.py | 40 +++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/sphinx/domains/python/_annotations.py b/sphinx/domains/python/_annotations.py index 5d4803cfb60..35525f6b1b3 100644 --- a/sphinx/domains/python/_annotations.py +++ b/sphinx/domains/python/_annotations.py @@ -161,7 +161,29 @@ def unparse(node: ast.AST) -> list[Node]: addnodes.desc_sig_punctuation('', ')')] return result - raise SyntaxError # unsupported syntax + if isinstance(node, ast.Call): + # Call nodes can be used in Annotated type metadata, + # for example Annotated[str, ArbitraryTypeValidator(str, len=10)] + args = [] + for arg in node.args: + args += unparse(arg) + args.append(addnodes.desc_sig_punctuation('', ',')) + args.append(addnodes.desc_sig_space()) + for kwd in node.keywords: + args.append(addnodes.desc_sig_name(kwd.arg, kwd.arg)) # type: ignore[arg-type] + args.append(addnodes.desc_sig_operator('', '=')) + args += unparse(kwd.value) + args.append(addnodes.desc_sig_punctuation('', ',')) + args.append(addnodes.desc_sig_space()) + result = [ + *unparse(node.func), + addnodes.desc_sig_punctuation('', '('), + *args[:-2], # skip the final comma and space + addnodes.desc_sig_punctuation('', ')'), + ] + return result + msg = f'unsupported syntax: {node}' + raise SyntaxError(msg) # unsupported syntax def _unparse_pep_604_annotation(node: ast.Subscript) -> list[Node]: subscript = node.slice diff --git a/tests/test_domains/test_domain_py.py b/tests/test_domains/test_domain_py.py index 08390b7113e..ce3d44465eb 100644 --- a/tests/test_domains/test_domain_py.py +++ b/tests/test_domains/test_domain_py.py @@ -370,6 +370,27 @@ def test_parse_annotation(app): [desc_sig_punctuation, "]"])) assert_node(doctree[0], pending_xref, refdomain="py", reftype="obj", reftarget="typing.Literal") + # Annotated type with callable gets parsed + doctree = _parse_annotation("Annotated[Optional[str], annotated_types.MaxLen(max_length=10)]", app.env) + assert_node(doctree, ( + [pending_xref, 'Annotated'], + [desc_sig_punctuation, '['], + [pending_xref, 'str'], + [desc_sig_space, ' '], + [desc_sig_punctuation, '|'], + [desc_sig_space, ' '], + [pending_xref, 'None'], + [desc_sig_punctuation, ','], + [desc_sig_space, ' '], + [pending_xref, 'annotated_types.MaxLen'], + [desc_sig_punctuation, '('], + [desc_sig_name, 'max_length'], + [desc_sig_operator, '='], + [desc_sig_literal_number, '10'], + [desc_sig_punctuation, ')'], + [desc_sig_punctuation, ']'], + )) + def test_parse_annotation_suppress(app): doctree = _parse_annotation("~typing.Dict[str, str]", app.env) @@ -802,7 +823,22 @@ def test_function_pep_695(app): [desc_sig_name, 'A'], [desc_sig_punctuation, ':'], desc_sig_space, - [desc_sig_name, ([pending_xref, 'int | Annotated[int, ctype("char")]'])], + [desc_sig_name, ( + [pending_xref, 'int'], + [desc_sig_space, ' '], + [desc_sig_punctuation, '|'], + [desc_sig_space, ' '], + [pending_xref, 'Annotated'], + [desc_sig_punctuation, '['], + [pending_xref, 'int'], + [desc_sig_punctuation, ','], + [desc_sig_space, ' '], + [pending_xref, 'ctype'], + [desc_sig_punctuation, '('], + [desc_sig_literal_string, "'char'"], + [desc_sig_punctuation, ')'], + [desc_sig_punctuation, ']'], + )], )], [desc_type_parameter, ( [desc_sig_operator, '*'], @@ -987,7 +1023,7 @@ def test_class_def_pep_696(app): ('[T:(*Ts)|int]', '[T: (*Ts) | int]'), ('[T:(int|(*Ts))]', '[T: (int | (*Ts))]'), ('[T:((*Ts)|int)]', '[T: ((*Ts) | int)]'), - ('[T:Annotated[int,ctype("char")]]', '[T: Annotated[int, ctype("char")]]'), + ("[T:Annotated[int,ctype('char')]]", "[T: Annotated[int, ctype('char')]]"), ]) def test_pep_695_and_pep_696_whitespaces_in_bound(app, tp_list, tptext): text = f'.. py:function:: f{tp_list}()' From db56cefac43bb6d00081e5085016e9c103eb2dc8 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Thu, 18 Jul 2024 13:18:05 +0100 Subject: [PATCH 2/8] Special-case dataclasses --- sphinx/ext/autodoc/__init__.py | 12 +++-- sphinx/util/inspect.py | 2 +- sphinx/util/typing.py | 34 +++++++++++-- .../test-ext-autodoc/target/annotated.py | 36 +++++++++++++- tests/test_extensions/test_ext_autodoc.py | 48 ++++++++++++++++++- tests/test_util/test_util_typing.py | 8 ++-- 6 files changed, 125 insertions(+), 15 deletions(-) diff --git a/sphinx/ext/autodoc/__init__.py b/sphinx/ext/autodoc/__init__.py index b3fb3e0c41f..41e128e0732 100644 --- a/sphinx/ext/autodoc/__init__.py +++ b/sphinx/ext/autodoc/__init__.py @@ -2008,7 +2008,8 @@ def import_object(self, raiseerror: bool = False) -> bool: with mock(self.config.autodoc_mock_imports): parent = import_module(self.modname, self.config.autodoc_warningiserror) annotations = get_type_hints(parent, None, - self.config.autodoc_type_aliases) + self.config.autodoc_type_aliases, + include_extras=True) if self.objpath[-1] in annotations: self.object = UNINITIALIZED_ATTR self.parent = parent @@ -2097,7 +2098,8 @@ def add_directive_header(self, sig: str) -> None: if self.config.autodoc_typehints != 'none': # obtain annotation for this data annotations = get_type_hints(self.parent, None, - self.config.autodoc_type_aliases) + self.config.autodoc_type_aliases, + include_extras=True) if self.objpath[-1] in annotations: if self.config.autodoc_typehints_format == "short": objrepr = stringify_annotation(annotations.get(self.objpath[-1]), @@ -2541,7 +2543,8 @@ class Foo: def is_uninitialized_instance_attribute(self, parent: Any) -> bool: """Check the subject is an annotation only attribute.""" - annotations = get_type_hints(parent, None, self.config.autodoc_type_aliases) + annotations = get_type_hints(parent, None, self.config.autodoc_type_aliases, + include_extras=True) return self.objpath[-1] in annotations def import_object(self, raiseerror: bool = False) -> bool: @@ -2673,7 +2676,8 @@ def add_directive_header(self, sig: str) -> None: if self.config.autodoc_typehints != 'none': # obtain type annotation for this attribute annotations = get_type_hints(self.parent, None, - self.config.autodoc_type_aliases) + self.config.autodoc_type_aliases, + include_extras=True) if self.objpath[-1] in annotations: if self.config.autodoc_typehints_format == "short": objrepr = stringify_annotation(annotations.get(self.objpath[-1]), diff --git a/sphinx/util/inspect.py b/sphinx/util/inspect.py index 04595fd8c58..28bba0c3032 100644 --- a/sphinx/util/inspect.py +++ b/sphinx/util/inspect.py @@ -652,7 +652,7 @@ def signature( try: # Resolve annotations using ``get_type_hints()`` and type_aliases. localns = TypeAliasNamespace(type_aliases) - annotations = typing.get_type_hints(subject, None, localns) + annotations = typing.get_type_hints(subject, None, localns, include_extras=True) for i, param in enumerate(parameters): if param.name in annotations: annotation = annotations[param.name] diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index a295c0605e6..eb38d048e76 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -2,6 +2,7 @@ from __future__ import annotations +import dataclasses import sys import types import typing @@ -157,6 +158,7 @@ def get_type_hints( obj: Any, globalns: dict[str, Any] | None = None, localns: dict[str, Any] | None = None, + include_extras: bool = False, ) -> dict[str, Any]: """Return a dictionary containing type hints for a function, method, module or class object. @@ -167,7 +169,7 @@ def get_type_hints( from sphinx.util.inspect import safe_getattr # lazy loading try: - return typing.get_type_hints(obj, globalns, localns) + return typing.get_type_hints(obj, globalns, localns, include_extras=include_extras) except NameError: # Failed to evaluate ForwardRef (maybe TYPE_CHECKING) return safe_getattr(obj, '__annotations__', {}) @@ -267,7 +269,20 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s return f':py:class:`{module_prefix}{_INVALID_BUILTIN_CLASSES[cls]}`' elif _is_annotated_form(cls): args = restify(cls.__args__[0], mode) - meta = ', '.join(map(repr, cls.__metadata__)) + meta_args = [] + for m in cls.__metadata__: + if isinstance(m, type): + meta_args.append(restify(m, mode)) + elif dataclasses.is_dataclass(m): + # use restify for the repr of field values rather than repr + d_fields = ', '.join([ + fr"{f.name}=\ {restify(getattr(m, f.name), mode)}" + for f in dataclasses.fields(m) if f.repr + ]) + meta_args.append(fr'{restify(type(m), mode)}\ ({d_fields})') + else: + meta_args.append(repr(m)) + meta = ', '.join(meta_args) if sys.version_info[:2] <= (3, 11): # Hardcoded to fix errors on Python 3.11 and earlier. return fr':py:class:`~typing.Annotated`\ [{args}, {meta}]' @@ -510,7 +525,20 @@ def stringify_annotation( return f'{module_prefix}Literal[{args}]' elif _is_annotated_form(annotation): # for py39+ args = stringify_annotation(annotation_args[0], mode) - meta = ', '.join(map(repr, annotation.__metadata__)) + meta_args = [] + for m in annotation.__metadata__: + if isinstance(m, type): + meta_args.append(stringify_annotation(m, mode)) + elif dataclasses.is_dataclass(m): + # use stringify_annotation for the repr of field values rather than repr + d_fields = ', '.join([ + f"{f.name}={stringify_annotation(getattr(m, f.name), mode)}" + for f in dataclasses.fields(m) if f.repr + ]) + meta_args.append(f'{stringify_annotation(type(m), mode)}({d_fields})') + else: + meta_args.append(repr(m)) + meta = ', '.join(meta_args) if sys.version_info[:2] <= (3, 11): if mode == 'fully-qualified-except-typing': return f'Annotated[{args}, {meta}]' diff --git a/tests/roots/test-ext-autodoc/target/annotated.py b/tests/roots/test-ext-autodoc/target/annotated.py index 5b87518f968..7adc3e0f152 100644 --- a/tests/roots/test-ext-autodoc/target/annotated.py +++ b/tests/roots/test-ext-autodoc/target/annotated.py @@ -1,8 +1,42 @@ -from __future__ import annotations +# from __future__ import annotations +import dataclasses +import types from typing import Annotated +@dataclasses.dataclass(frozen=True) +class FuncValidator: + func: types.FunctionType + + +@dataclasses.dataclass(frozen=True) +class MaxLen: + max_length: int + whitelisted_words: list[str] + + +def validate(value: str) -> str: + return value + + +#: Type alias for a validated string. +ValidatedString = Annotated[str, FuncValidator(validate)] + + def hello(name: Annotated[str, "attribute"]) -> None: """docstring""" pass + + +class AnnotatedAttributes: + """docstring""" + + #: Docstring about the ``name`` attribute. + name: Annotated[str, "attribute"] + + #: Docstring about the ``max_len`` attribute. + max_len: list[Annotated[str, MaxLen(10, ['word_one', 'word_two'])]] + + #: Docstring about the ``validated`` attribute. + validated: ValidatedString diff --git a/tests/test_extensions/test_ext_autodoc.py b/tests/test_extensions/test_ext_autodoc.py index 53289f32c1a..da2790a598d 100644 --- a/tests/test_extensions/test_ext_autodoc.py +++ b/tests/test_extensions/test_ext_autodoc.py @@ -2321,18 +2321,62 @@ def test_autodoc_TypeVar(app): @pytest.mark.sphinx('html', testroot='ext-autodoc') def test_autodoc_Annotated(app): - options = {"members": None} + options = {'members': None, 'member-order': 'bysource'} actual = do_autodoc(app, 'module', 'target.annotated', options) assert list(actual) == [ '', '.. py:module:: target.annotated', '', '', - '.. py:function:: hello(name: str) -> None', + '.. py:class:: FuncValidator(func: function)', + ' :module: target.annotated', + '', + '', + '.. py:class:: MaxLen(max_length: int, whitelisted_words: list[str])', + ' :module: target.annotated', + '', + '', + '.. py:data:: ValidatedString', + ' :module: target.annotated', + '', + ' Type alias for a validated string.', + '', + ' alias of :py:class:`~typing.Annotated`\\ [:py:class:`str`, ' + '~target.annotated.FuncValidator(func=:py:class:`~target.annotated.validate`)]', + '', + '', + ".. py:function:: hello(name: ~typing.Annotated[str, 'attribute']) -> None", + ' :module: target.annotated', + '', + ' docstring', + '', + '', + '.. py:class:: AnnotatedAttributes()', ' :module: target.annotated', '', ' docstring', '', + '', + ' .. py:attribute:: AnnotatedAttributes.name', + ' :module: target.annotated', + " :type: ~typing.Annotated[str, 'attribute']", + '', + ' Docstring about the ``name`` attribute.', + '', + '', + ' .. py:attribute:: AnnotatedAttributes.max_len', + ' :module: target.annotated', + " :type: list[~typing.Annotated[str, ~target.annotated.MaxLen(max_length=10, whitelisted_words=['word_one', 'word_two'])]]", + '', + ' Docstring about the ``max_len`` attribute.', + '', + '', + ' .. py:attribute:: AnnotatedAttributes.validated', + ' :module: target.annotated', + ' :type: ~typing.Annotated[str, ~target.annotated.FuncValidator(func=~target.annotated.validate)]', + '', + ' Docstring about the ``validated`` attribute.', + '', ] diff --git a/tests/test_util/test_util_typing.py b/tests/test_util/test_util_typing.py index d00d69fb04f..26f3cb8e9ba 100644 --- a/tests/test_util/test_util_typing.py +++ b/tests/test_util/test_util_typing.py @@ -196,8 +196,8 @@ def test_restify_type_hints_containers(): def test_restify_Annotated(): assert restify(Annotated[str, "foo", "bar"]) == ":py:class:`~typing.Annotated`\\ [:py:class:`str`, 'foo', 'bar']" assert restify(Annotated[str, "foo", "bar"], 'smart') == ":py:class:`~typing.Annotated`\\ [:py:class:`str`, 'foo', 'bar']" - assert restify(Annotated[float, Gt(-10.0)]) == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, Gt(gt=-10.0)]' - assert restify(Annotated[float, Gt(-10.0)], 'smart') == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, Gt(gt=-10.0)]' + assert restify(Annotated[float, Gt(-10.0)]) == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`tests.test_util.test_util_typing.Gt`\\ (gt=-10.0)]' + assert restify(Annotated[float, Gt(-10.0)], 'smart') == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`~tests.test_util.test_util_typing.Gt`\\ (gt=-10.0)]' def test_restify_type_hints_Callable(): @@ -525,8 +525,8 @@ def test_stringify_type_hints_pep_585(): def test_stringify_Annotated(): assert stringify_annotation(Annotated[str, "foo", "bar"], 'fully-qualified-except-typing') == "Annotated[str, 'foo', 'bar']" assert stringify_annotation(Annotated[str, "foo", "bar"], 'smart') == "~typing.Annotated[str, 'foo', 'bar']" - assert stringify_annotation(Annotated[float, Gt(-10.0)], 'fully-qualified-except-typing') == "Annotated[float, Gt(gt=-10.0)]" - assert stringify_annotation(Annotated[float, Gt(-10.0)], 'smart') == "~typing.Annotated[float, Gt(gt=-10.0)]" + assert stringify_annotation(Annotated[float, Gt(-10.0)], 'fully-qualified-except-typing') == "Annotated[float, tests.test_util.test_util_typing.Gt(gt=-10.0)]" + assert stringify_annotation(Annotated[float, Gt(-10.0)], 'smart') == "~typing.Annotated[float, ~tests.test_util.test_util_typing.Gt(gt=-10.0)]" def test_stringify_Unpack(): From ef841c1ac347581331f358f580c57aa12680829a Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 20 Jul 2024 04:56:49 +0100 Subject: [PATCH 3/8] Fix autodoc tests --- tests/test_extensions/test_ext_autodoc.py | 2 +- tests/test_util/test_util_typing.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_extensions/test_ext_autodoc.py b/tests/test_extensions/test_ext_autodoc.py index da2790a598d..e10850bacd7 100644 --- a/tests/test_extensions/test_ext_autodoc.py +++ b/tests/test_extensions/test_ext_autodoc.py @@ -2342,7 +2342,7 @@ def test_autodoc_Annotated(app): ' Type alias for a validated string.', '', ' alias of :py:class:`~typing.Annotated`\\ [:py:class:`str`, ' - '~target.annotated.FuncValidator(func=:py:class:`~target.annotated.validate`)]', + ':py:class:`~target.annotated.FuncValidator`\\ (func=\\ :py:class:`~target.annotated.validate`)]', '', '', ".. py:function:: hello(name: ~typing.Annotated[str, 'attribute']) -> None", diff --git a/tests/test_util/test_util_typing.py b/tests/test_util/test_util_typing.py index 26f3cb8e9ba..d81c128b76b 100644 --- a/tests/test_util/test_util_typing.py +++ b/tests/test_util/test_util_typing.py @@ -196,8 +196,8 @@ def test_restify_type_hints_containers(): def test_restify_Annotated(): assert restify(Annotated[str, "foo", "bar"]) == ":py:class:`~typing.Annotated`\\ [:py:class:`str`, 'foo', 'bar']" assert restify(Annotated[str, "foo", "bar"], 'smart') == ":py:class:`~typing.Annotated`\\ [:py:class:`str`, 'foo', 'bar']" - assert restify(Annotated[float, Gt(-10.0)]) == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`tests.test_util.test_util_typing.Gt`\\ (gt=-10.0)]' - assert restify(Annotated[float, Gt(-10.0)], 'smart') == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`~tests.test_util.test_util_typing.Gt`\\ (gt=-10.0)]' + assert restify(Annotated[float, Gt(-10.0)]) == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`tests.test_util.test_util_typing.Gt`\\ (gt=\ -10.0)]' + assert restify(Annotated[float, Gt(-10.0)], 'smart') == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`~tests.test_util.test_util_typing.Gt`\\ (gt=\ -10.0)]' def test_restify_type_hints_Callable(): From 22d07ca9f36dd64bb71b0a7712b35e995cb38506 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 20 Jul 2024 05:21:38 +0100 Subject: [PATCH 4/8] escape backslashes --- tests/test_util/test_util_typing.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_util/test_util_typing.py b/tests/test_util/test_util_typing.py index d81c128b76b..7fea9a2c4f3 100644 --- a/tests/test_util/test_util_typing.py +++ b/tests/test_util/test_util_typing.py @@ -196,8 +196,8 @@ def test_restify_type_hints_containers(): def test_restify_Annotated(): assert restify(Annotated[str, "foo", "bar"]) == ":py:class:`~typing.Annotated`\\ [:py:class:`str`, 'foo', 'bar']" assert restify(Annotated[str, "foo", "bar"], 'smart') == ":py:class:`~typing.Annotated`\\ [:py:class:`str`, 'foo', 'bar']" - assert restify(Annotated[float, Gt(-10.0)]) == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`tests.test_util.test_util_typing.Gt`\\ (gt=\ -10.0)]' - assert restify(Annotated[float, Gt(-10.0)], 'smart') == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`~tests.test_util.test_util_typing.Gt`\\ (gt=\ -10.0)]' + assert restify(Annotated[float, Gt(-10.0)]) == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`tests.test_util.test_util_typing.Gt`\\ (gt=\\ -10.0)]' + assert restify(Annotated[float, Gt(-10.0)], 'smart') == ':py:class:`~typing.Annotated`\\ [:py:class:`float`, :py:class:`~tests.test_util.test_util_typing.Gt`\\ (gt=\\ -10.0)]' def test_restify_type_hints_Callable(): From 932a1554f99c3e50183cb5e1ce45ebab04cf9a3a Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 20 Jul 2024 05:27:31 +0100 Subject: [PATCH 5/8] XFAIL --- tests/test_builders/test_build_manpage.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_builders/test_build_manpage.py b/tests/test_builders/test_build_manpage.py index 0958b30f302..31d75d64d59 100644 --- a/tests/test_builders/test_build_manpage.py +++ b/tests/test_builders/test_build_manpage.py @@ -7,6 +7,8 @@ from sphinx.config import Config +@pytest.mark.xfail(docutils.__version_info__[:2] > (0, 21), + reason='Docutils has removed the reference key in master') @pytest.mark.sphinx('man') def test_all(app, status, warning): app.build(force_all=True) @@ -47,6 +49,8 @@ def test_man_make_section_directory(app, status, warning): assert (app.outdir / 'man1' / 'projectnamenotset.1').exists() +@pytest.mark.xfail(docutils.__version_info__[:2] > (0, 21), + reason='Docutils has removed the reference key in master') @pytest.mark.sphinx('man', testroot='directive-code') def test_captioned_code_block(app, status, warning): app.build(force_all=True) From 0b29b445056aa9058a6efe65640935d532219f89 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 20 Jul 2024 10:38:14 +0100 Subject: [PATCH 6/8] Fix py39 --- sphinx/util/typing.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/sphinx/util/typing.py b/sphinx/util/typing.py index eb38d048e76..a5d24170576 100644 --- a/sphinx/util/typing.py +++ b/sphinx/util/typing.py @@ -539,6 +539,11 @@ def stringify_annotation( else: meta_args.append(repr(m)) meta = ', '.join(meta_args) + if sys.version_info[:2] <= (3, 9): + if mode == 'smart': + return f'~typing.Annotated[{args}, {meta}]' + if mode == 'fully-qualified': + return f'typing.Annotated[{args}, {meta}]' if sys.version_info[:2] <= (3, 11): if mode == 'fully-qualified-except-typing': return f'Annotated[{args}, {meta}]' From cfee2be7b33337ae9baf3864bf63ccc2cf158041 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 20 Jul 2024 10:44:53 +0100 Subject: [PATCH 7/8] XPASS --- tests/test_util/test_util_typing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/test_util/test_util_typing.py b/tests/test_util/test_util_typing.py index 7fea9a2c4f3..956cffe9dec 100644 --- a/tests/test_util/test_util_typing.py +++ b/tests/test_util/test_util_typing.py @@ -521,7 +521,6 @@ def test_stringify_type_hints_pep_585(): assert stringify_annotation(tuple[List[dict[int, str]], str, ...], "smart") == "tuple[~typing.List[dict[int, str]], str, ...]" -@pytest.mark.xfail(sys.version_info[:2] <= (3, 9), reason='Needs fixing.') def test_stringify_Annotated(): assert stringify_annotation(Annotated[str, "foo", "bar"], 'fully-qualified-except-typing') == "Annotated[str, 'foo', 'bar']" assert stringify_annotation(Annotated[str, "foo", "bar"], 'smart') == "~typing.Annotated[str, 'foo', 'bar']" From 8c96a2ad5ed6b1b4221e1f1c4572247a0a7fa951 Mon Sep 17 00:00:00 2001 From: Adam Turner <9087854+aa-turner@users.noreply.github.com> Date: Sat, 20 Jul 2024 11:19:24 +0100 Subject: [PATCH 8/8] CHANGES --- CHANGES.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c53bb8f8b0f..66150d2624a 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -11,6 +11,10 @@ Bugs fixed * #12601, #12625: Support callable objects in :py:class:`~typing.Annotated` type metadata in the Python domain. Patch by Adam Turner. +* #12601, #12622: Resolve :py:class:`~typing.Annotated` warnings with + ``sphinx.ext.autodoc``, + especially when using :mod:`dataclasses` as type metadata. + Patch by Adam Turner. Release 7.4.6 (released Jul 18, 2024) =====================================