Skip to content

Commit

Permalink
Restore support for typing.ParamSpec (#12581)
Browse files Browse the repository at this point in the history
  • Loading branch information
AA-Turner authored Jul 15, 2024
1 parent 18ac58b commit 73dd9fc
Show file tree
Hide file tree
Showing 3 changed files with 42 additions and 3 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ Bugs fixed

* Fix invalid HTML when a rubric node with invalid ``heading-level`` is used.
Patch by Adam Turner.
* #12579, #12581: Restore support for ``typing.ParamSpec`` in autodoc.
Patch by Adam Turner.

Testing
-------
Expand Down
10 changes: 7 additions & 3 deletions sphinx/util/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,11 @@ def _is_unpack_form(obj: Any) -> bool:

def _typing_internal_name(obj: Any) -> str | None:
if sys.version_info[:2] >= (3, 10):
return obj.__name__
try:
return obj.__name__
except AttributeError:
# e.g. ParamSpecArgs, ParamSpecKwargs
return ''
return getattr(obj, '_name', None)


Expand All @@ -237,7 +241,7 @@ def restify(cls: Any, mode: _RestifyMode = 'fully-qualified-except-typing') -> s
raise ValueError(msg)

# things that are not types
if cls in {None, NoneType}:
if cls is None or cls == NoneType:
return ':py:obj:`None`'
if cls is Ellipsis:
return '...'
Expand Down Expand Up @@ -388,7 +392,7 @@ def stringify_annotation(
raise ValueError(msg)

# things that are not types
if annotation in {None, NoneType}:
if annotation is None or annotation == NoneType:
return 'None'
if annotation is Ellipsis:
return '...'
Expand Down
33 changes: 33 additions & 0 deletions tests/test_util/test_util_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,21 @@ def test_restify_mock():
assert restify(unknown.secret.Class, "smart") == ':py:class:`~unknown.secret.Class`'


@pytest.mark.xfail(sys.version_info[:2] <= (3, 9), reason='ParamSpec not supported in Python 3.9.')
def test_restify_type_hints_paramspec():
from typing import ParamSpec
P = ParamSpec('P')

assert restify(P) == ":py:obj:`tests.test_util.test_util_typing.P`"
assert restify(P, "smart") == ":py:obj:`~tests.test_util.test_util_typing.P`"

assert restify(P.args) == "P.args"
assert restify(P.args, "smart") == "P.args"

assert restify(P.kwargs) == "P.kwargs"
assert restify(P.kwargs, "smart") == "P.kwargs"


def test_stringify_annotation():
assert stringify_annotation(int, 'fully-qualified-except-typing') == "int"
assert stringify_annotation(int, "smart") == "int"
Expand Down Expand Up @@ -722,3 +737,21 @@ def test_stringify_type_ForwardRef():
assert stringify_annotation(Tuple[dict[ForwardRef("MyInt"), str], list[List[int]]]) == "Tuple[dict[MyInt, str], list[List[int]]]" # type: ignore[attr-defined]
assert stringify_annotation(Tuple[dict[ForwardRef("MyInt"), str], list[List[int]]], 'fully-qualified-except-typing') == "Tuple[dict[MyInt, str], list[List[int]]]" # type: ignore[attr-defined]
assert stringify_annotation(Tuple[dict[ForwardRef("MyInt"), str], list[List[int]]], 'smart') == "~typing.Tuple[dict[MyInt, str], list[~typing.List[int]]]" # type: ignore[attr-defined]


@pytest.mark.xfail(sys.version_info[:2] <= (3, 9), reason='ParamSpec not supported in Python 3.9.')
def test_stringify_type_hints_paramspec():
from typing import ParamSpec
P = ParamSpec('P')

assert stringify_annotation(P, 'fully-qualified') == "~P"
assert stringify_annotation(P, 'fully-qualified-except-typing') == "~P"
assert stringify_annotation(P, "smart") == "~P"

assert stringify_annotation(P.args, 'fully-qualified') == "typing.~P"
assert stringify_annotation(P.args, 'fully-qualified-except-typing') == "~P"
assert stringify_annotation(P.args, "smart") == "~typing.~P"

assert stringify_annotation(P.kwargs, 'fully-qualified') == "typing.~P"
assert stringify_annotation(P.kwargs, 'fully-qualified-except-typing') == "~P"
assert stringify_annotation(P.kwargs, "smart") == "~typing.~P"

0 comments on commit 73dd9fc

Please sign in to comment.