From: Vladimír Vondruš Date: Mon, 16 Sep 2024 16:33:20 +0000 (+0200) Subject: documentation/python: adopt PEP585 naming for pybind11 typing hints. X-Git-Url: https://www.chiark.greenend.org.uk/ucgi/~cjwatson/git?a=commitdiff_plain;h=a6311f4db9dd105a565673454453515524f631de;p=blog.git documentation/python: adopt PEP585 naming for pybind11 typing hints. This is what pybind11 2.12+ does, but as I have to parse the docstrings and prepend the `typing.` namespace, I can retrospectively make this change for older pybind11 versions as well. In particular, the following is changed in type annotations of generated documentation, independently of pybind11 version: - typing.Tuple is now tuple - typing.List is now list - typing.Dict is now dict - typing.Set is now set If the python standard library inventory file is used, the type annotations in signatures will now link to the builtin types instead of the typing module. Note that if your documentation uses external docstrings and matches overloads by the signature, you may need to adapt the rename to make them match again. --- diff --git a/documentation/python.py b/documentation/python.py index 176e0fd2..6c8274c0 100755 --- a/documentation/python.py +++ b/documentation/python.py @@ -908,7 +908,14 @@ def _pybind11_extract_default_argument(string): raise SyntaxError("Unexpected end of `{}`".format(string)) def _pybind_map_name_prefix_or_add_typing_suffix(state: State, input_type: str): - if input_type in ['Callable', 'Dict', 'Iterator', 'Iterable', 'List', 'Optional', 'Set', 'Tuple', 'Union']: + # As of pybind11 2.12, the names match https://peps.python.org/pep-0585/ + # which replaces the original typing.List, Dict etc. with actual builtin + # types to avoid duplication. To make testing simpler, this tool makes them + # follow PEP585 with older pybind11 as well. + input_type_lowercase = input_type.lower() + if input_type_lowercase in ['dict', 'list', 'set', 'tuple']: + return input_type_lowercase + if input_type in ['Callable', 'Iterator', 'Iterable', 'Optional', 'Union']: return 'typing.' + input_type else: return map_name_prefix(state, input_type) diff --git a/documentation/test_python/pybind_external_overload_docs/docs.rst b/documentation/test_python/pybind_external_overload_docs/docs.rst index 2499e017..9a99c092 100644 --- a/documentation/test_python/pybind_external_overload_docs/docs.rst +++ b/documentation/test_python/pybind_external_overload_docs/docs.rst @@ -1,10 +1,10 @@ -.. py:function:: pybind_external_overload_docs.foo(a: int, b: typing.Tuple[int, str]) +.. py:function:: pybind_external_overload_docs.foo(a: int, b: tuple[int, str]) :param a: First parameter :param b: Second parameter Details for the first overload. -.. py:function:: pybind_external_overload_docs.foo(arg0: typing.Callable[[float, typing.List[float]], int]) +.. py:function:: pybind_external_overload_docs.foo(arg0: typing.Callable[[float, list[float]], int]) :param arg0: The caller Complex signatures in the second overload should be matched properly, too. diff --git a/documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.html b/documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.html index f0f54110..44841302 100644 --- a/documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.html +++ b/documentation/test_python/pybind_external_overload_docs/pybind_external_overload_docs.html @@ -48,12 +48,12 @@

Functions

- def foo(a: int, - b: typing.Tuple[int, str]) -> None + def foo(a: int, + b: tuple[int, str]) -> None
First overload
- def foo(arg0: typing.Callable[[float, typing.List[float]], int], /) -> None + def foo(arg0: typing.Callable[[float, list[float]], int], /) -> None
Second overload
@@ -72,10 +72,10 @@

Function documentation

-
+

- def pybind_external_overload_docs.foo(a: int, - b: typing.Tuple[int, str]) -> None + def pybind_external_overload_docs.foo(a: int, + b: tuple[int, str]) -> None

First overload

@@ -95,9 +95,9 @@

Details for the first overload.

-
+

- def pybind_external_overload_docs.foo(arg0: typing.Callable[[float, typing.List[float]], int], /) -> None + def pybind_external_overload_docs.foo(arg0: typing.Callable[[float, list[float]], int], /) -> None

Second overload

diff --git a/documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html b/documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html index f0664f36..bb4942ec 100644 --- a/documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html +++ b/documentation/test_python/pybind_signatures/pybind_signatures.MyClass.html @@ -57,13 +57,13 @@
def instance_function(self, arg0: int, - arg1: str, /) -> typing.Tuple[float, int] + arg1: str, /) -> tuple[float, int]
Instance method with positional-only args
def instance_function_kwargs(self, hey: int, - what: str = '<eh?>') -> typing.Tuple[float, int] + what: str = '<eh?>') -> tuple[float, int]
Instance method with position or keyword args
diff --git a/documentation/test_python/pybind_signatures/pybind_signatures.html b/documentation/test_python/pybind_signatures/pybind_signatures.html index 43ef942b..83ef7d4a 100644 --- a/documentation/test_python/pybind_signatures/pybind_signatures.html +++ b/documentation/test_python/pybind_signatures/pybind_signatures.html @@ -98,7 +98,7 @@
Scale an integer, kwargs
- def takes_a_function(arg0: typing.Callable[[float, typing.List[float]], int], /) -> None + def takes_a_function(arg0: typing.Callable[[float, list[float]], int], /) -> None
A function taking a Callable
@@ -106,7 +106,7 @@
A function taking a Callable that returns None
- def taking_a_list_returning_a_tuple(arg0: typing.List[float], /) -> typing.Tuple[int, int, int] + def taking_a_list_returning_a_tuple(arg0: list[float], /) -> tuple[int, int, int]
Takes a list, returns a tuple
diff --git a/documentation/test_python/pybind_type_links/pybind_type_links.cpp b/documentation/test_python/pybind_type_links/pybind_type_links.cpp index eed8fb5a..0d6e9340 100644 --- a/documentation/test_python/pybind_type_links/pybind_type_links.cpp +++ b/documentation/test_python/pybind_type_links/pybind_type_links.cpp @@ -17,7 +17,7 @@ struct Foo { Foo typeReturn() { return {}; } -void typeNested(const std::pair>&) {} +void typeNested(const std::pair>&, const std::set&, const std::map&) {} void typeNestedEnumAndDefault(std::pair) {} diff --git a/documentation/test_python/pybind_type_links/pybind_type_links.html b/documentation/test_python/pybind_type_links/pybind_type_links.html index 09fc4d50..bb9afa3f 100644 --- a/documentation/test_python/pybind_type_links/pybind_type_links.html +++ b/documentation/test_python/pybind_type_links/pybind_type_links.html @@ -62,11 +62,13 @@
A function taking an enum with a default
- def type_nested(arg0: typing.Tuple[Foo, typing.List[Enum]], /) -> None + def type_nested(arg0: tuple[Foo, list[Enum]], + arg1: set[Enum], + arg2: dict[int, Foo], /) -> None
A function with nested type annotation
- def type_nested_enum_and_default(value: typing.Tuple[int, Enum] = (3, Enum.FIRST)) -> None + def type_nested_enum_and_default(value: tuple[int, Enum] = (3, Enum.FIRST)) -> None
A function taking a nested enum with a default. This won't have a link.
diff --git a/documentation/test_python/test_pybind.py b/documentation/test_python/test_pybind.py index 97e16da6..73db6017 100644 --- a/documentation/test_python/test_pybind.py +++ b/documentation/test_python/test_pybind.py @@ -97,28 +97,43 @@ class Signature(unittest.TestCase): ], None, None)) def test_square_brackets(self): - self.assertEqual(parse_pybind_signature(self.state, [], - 'foo(a: Tuple[int, str], no_really: str) -> List[str]'), - ('foo', '', [ - ('a', 'typing.Tuple[int, str]', 'typing.Tuple[int, str]', None), - ('no_really', 'str', 'str', None), - ], 'typing.List[str]', 'typing.List[str]')) + for i in [ + # pybind11 2.11 and older + 'foo(a: Tuple[int, str], no_really: str) -> List[str]', + # pybind11 2.12+ + 'foo(a: tuple[int, str], no_really: str) -> list[str]' + ]: + self.assertEqual(parse_pybind_signature(self.state, [], i), + ('foo', '', [ + ('a', 'tuple[int, str]', 'tuple[int, str]', None), + ('no_really', 'str', 'str', None), + ], 'list[str]', 'list[str]')) def test_nested_square_brackets(self): - self.assertEqual(parse_pybind_signature(self.state, [], - 'foo(a: Tuple[int, List[Tuple[int, int]]], another: float) -> Union[str, None]'), - ('foo', '', [ - ('a', 'typing.Tuple[int, typing.List[typing.Tuple[int, int]]]', 'typing.Tuple[int, typing.List[typing.Tuple[int, int]]]', None), - ('another', 'float', 'float', None), - ], 'typing.Union[str, None]', 'typing.Union[str, None]')) + for i in [ + # pybind11 2.11 and older + 'foo(a: Tuple[int, Set[Tuple[int, int]]], another: float) -> Union[str, None]', + # pybind11 2.12+ + 'foo(a: tuple[int, set[tuple[int, int]]], another: float) -> Union[str, None]' + ]: + self.assertEqual(parse_pybind_signature(self.state, [], i), + ('foo', '', [ + ('a', 'tuple[int, set[tuple[int, int]]]', 'tuple[int, set[tuple[int, int]]]', None), + ('another', 'float', 'float', None), + ], 'typing.Union[str, None]', 'typing.Union[str, None]')) def test_callable(self): - self.assertEqual(parse_pybind_signature(self.state, [], - 'foo(a: Callable[[int, Tuple[int, int]], float], another: float)'), - ('foo', '', [ - ('a', 'typing.Callable[[int, typing.Tuple[int, int]], float]', 'typing.Callable[[int, typing.Tuple[int, int]], float]', None), - ('another', 'float', 'float', None), - ], None, None)) + for i in [ + # pybind11 2.11 and older + 'foo(a: Callable[[int, Dict[int, int]], float], another: float)', + # pybind11 2.12+ + 'foo(a: Callable[[int, dict[int, int]], float], another: float)' + ]: + self.assertEqual(parse_pybind_signature(self.state, [], i), + ('foo', '', [ + ('a', 'typing.Callable[[int, dict[int, int]], float]', 'typing.Callable[[int, dict[int, int]], float]', None), + ('another', 'float', 'float', None), + ], None, None)) def test_kwargs(self): self.assertEqual(parse_pybind_signature(self.state, [], @@ -154,13 +169,17 @@ class Signature(unittest.TestCase): ('b', None, None, '3'), ], None, None)) - self.assertEqual(parse_pybind_signature(self.state, [], - 'foo(a: Tuple[int, ...] = (1,("hello", \'world\'),3,4))'), - ('foo', '', [ - ('a', 'typing.Tuple[int, ...]', - 'typing.Tuple[int, ...]', - '(1,("hello", \'world\'),3,4)') - ], None, None)) + for i in [ + # pybind11 2.11 and older + 'foo(a: Tuple[int, ...] = (1,("hello", \'world\'),3,4))', + # pybind11 2.12+ + 'foo(a: tuple[int, ...] = (1,("hello", \'world\'),3,4))', + ]: + self.assertEqual(parse_pybind_signature(self.state, [], i), + ('foo', '', [ + ('a', 'tuple[int, ...]', 'tuple[int, ...]', + '(1,("hello", \'world\'),3,4)') + ], None, None)) self.assertEqual(parse_pybind_signature(self.state, [], 'foo(a: str = [dict(key="A", value=\'B\')["key"][0], None][0])'), @@ -233,9 +252,18 @@ class Signature(unittest.TestCase): def test_bad_return_type(self): bad_signature = ('foo', '', [('…', None, None, None)], None, None) - self.assertEqual(parse_pybind_signature(self.state, [], 'foo() -> List[[]'), bad_signature) - self.assertEqual(parse_pybind_signature(self.state, [], 'foo() -> List]'), bad_signature) - self.assertEqual(parse_pybind_signature(self.state, [], 'foo() -> ::std::vector'), bad_signature) + for i in [ + # pybind11 2.11 and older + 'foo() -> List[[]', + 'foo() -> List]', + # pybind11 2.12+ + 'foo() -> list[[]', + 'foo() -> list]', + # C++ leaked into the signature + 'foo() -> ::std::vector' + ]: + self.assertEqual(parse_pybind_signature(self.state, [], i), + bad_signature) def test_crazy_stuff(self): self.assertEqual(parse_pybind_signature(self.state, [], @@ -243,9 +271,14 @@ class Signature(unittest.TestCase): ('foo', '', [('…', None, None, None)], None, None)) def test_crazy_stuff_nested(self): - self.assertEqual(parse_pybind_signature(self.state, [], - 'foo(a: int, b: List[Math::Vector<4, UnsignedInt>])'), - ('foo', '', [('…', None, None, None)], None, None)) + for i in [ + # pybind11 2.11 and older + 'foo(a: int, b: List[Math::Vector<4, UnsignedInt>])', + # pybind11 2.12+ + 'foo(a: int, b: list[Math::Vector<4, UnsignedInt>])' + ]: + self.assertEqual(parse_pybind_signature(self.state, [], i), + ('foo', '', [('…', None, None, None)], None, None)) def test_crazy_stuff_docs(self): self.assertEqual(parse_pybind_signature(self.state, [], @@ -258,9 +291,14 @@ class Signature(unittest.TestCase): ('foo', '', [('…', None, None, None)], None, None)) def test_crazy_return_nested(self): - self.assertEqual(parse_pybind_signature(self.state, [], - 'foo(a: int) -> List[Math::Vector<4, UnsignedInt>]'), - ('foo', '', [('…', None, None, None)], None, None)) + for i in [ + # pybind11 2.11 and older + 'foo(a: int) -> List[Math::Vector<4, UnsignedInt>]', + # pybind11 2.12+ + 'foo(a: int) -> list[Math::Vector<4, UnsignedInt>]' + ]: + self.assertEqual(parse_pybind_signature(self.state, [], i), + ('foo', '', [('…', None, None, None)], None, None)) def test_crazy_return_docs(self): self.assertEqual(parse_pybind_signature(self.state, [], @@ -276,10 +314,17 @@ class Signature(unittest.TestCase): state = copy.deepcopy(self.state) state.name_mapping['module._module'] = 'module' - self.assertEqual(parse_pybind_signature(state, [], - 'foo(a: module._module.Foo, b: typing.Tuple[int, module._module.Bar]) -> module._module.Baz'), - ('foo', '', [('a', 'module.Foo', 'module.Foo', None), - ('b', 'typing.Tuple[int, module.Bar]', 'typing.Tuple[int, module.Bar]', None)], 'module.Baz', 'module.Baz')) + for i in [ + # pybind11 2.11 and older + 'foo(a: module._module.Foo, b: Tuple[int, module._module.Bar]) -> module._module.Baz', + # pybind11 2.12+ + 'foo(a: module._module.Foo, b: tuple[int, module._module.Bar]) -> module._module.Baz' + ]: + self.assertEqual(parse_pybind_signature(state, [], i), + ('foo', '', [ + ('a', 'module.Foo', 'module.Foo', None), + ('b', 'tuple[int, module.Bar]', 'tuple[int, module.Bar]', None) + ], 'module.Baz', 'module.Baz')) class Signatures(BaseInspectTestCase): def test_positional_args(self): diff --git a/documentation/test_python/test_search.py b/documentation/test_python/test_search.py index 1263db8c..f8a19fec 100644 --- a/documentation/test_python/test_search.py +++ b/documentation/test_python/test_search.py @@ -229,9 +229,9 @@ search_long_suffix_length [4] many_parameters [0, 2] | ($ | ) [1, 3] -0: .many_parameters(arg0: typing.Tuple[float, int, str, typing.List[…) [prefix=4[:30], suffix_length=53, type=FUNCTION] -> #many_parameters-06151 +0: .many_parameters(arg0: tuple[float, int, str, list[tuple[int, int…) [prefix=4[:30], suffix_length=53, type=FUNCTION] -> #many_parameters-a4d3e 1: [prefix=0[:52], suffix_length=51, type=FUNCTION] -> -2: .many_parameters(arg0: typing.Tuple[int, float, str, typing.List[…) [prefix=4[:30], suffix_length=53, type=FUNCTION] -> #many_parameters-31300 +2: .many_parameters(arg0: tuple[int, float, str, list[tuple[int, int…) [prefix=4[:30], suffix_length=53, type=FUNCTION] -> #many_parameters-99883 3: [prefix=2[:52], suffix_length=51, type=FUNCTION] -> 4: search_long_suffix_length [type=MODULE] -> search_long_suffix_length.html (EntryType.PAGE, CssClass.SUCCESS, 'page'), diff --git a/package/ci/circleci.yml b/package/ci/circleci.yml index c406dee1..520910b7 100644 --- a/package/ci/circleci.yml +++ b/package/ci/circleci.yml @@ -314,7 +314,7 @@ jobs: - install-base: extra: graphviz cmake ninja-build wget - install-python-deps: - # NumPy 2.0 doesn't work with pybind 2.11, see below + # NumPy 2.0 doesn't work with pybind < 2.12 numpy-version: ==1.26.4 # Ubuntu 22.04 has pygments 2.11 pygments-version: ==2.11.0 @@ -323,10 +323,8 @@ jobs: - test-plugins - test-documentation-themes: python-version: "3.10" - # 2.12.0 is the first that works with NumPy 2.0, unfortunately it has - # different docstring typing annotations to which I need to adapt tests - # first. Pinning to a version before together with NumPy 1. - pybind-version: "2.11.1" + # 2.9 series seems to be the first to work with Python 3.10 + pybind-version: "2.9.2" # 1.9.1 is in Ubuntu 22.04 repos, HOWEVER both 1.9.1 and 1.9.2 link # against libclang-9.so.1, which is stupid. I think I even asked if the # builds could be fixed back then, but of course they didn't even @@ -340,7 +338,7 @@ jobs: - install-base: extra: graphviz cmake ninja-build wget - install-python-deps: - # NumPy 2.0 doesn't work with pybind 2.11, see below + # NumPy 2.0 doesn't work with pybind < 2.12 numpy-version: ==1.26.4 # Ubuntu 24.04 has pygments 2.17 pygments-version: ==2.17.0 @@ -349,9 +347,8 @@ jobs: - test-plugins - test-documentation-themes: python-version: "3.11" - # 2.12.0 is the first that works with NumPy 2.0, unfortunately it has - # different docstring typing annotations to which I need to adapt tests - # first. Pinning to a version before together with NumPy 1. + # 2.11.1 is the last that uses pre-PEP585 typing annotations, make sure + # it's explicitly tested pybind-version: "2.11.1" # 1.9.8 is in Ubuntu 24.04 repos doxygen-version: "1.9.8" @@ -362,18 +359,15 @@ jobs: steps: - install-base: extra: graphviz cmake ninja-build wget - - install-python-deps: - # NumPy 2.0 doesn't work with pybind 2.11, see below - numpy-version: ==1.26.4 + - install-python-deps - checkout - test-theme - test-plugins - test-documentation-themes: python-version: "3.12" - # 2.12.0 is the first that works with NumPy 2.0, unfortunately it has - # different docstring typing annotations to which I need to adapt tests - # first. Pinning to a version before together with NumPy 1. - pybind-version: "2.11.1" + # 2.12 series is the first that uses PEP585 and first that works with + # NumPy 2.0 + pybind-version: "2.12.1" # 1.11 is the latest that passes all tests, 1.12 first needs # https://github.com/doxygen/doxygen/pull/11141 merged to be usable. doxygen-version: "1.11.0"