From 88824b5af6df1bc8b0361dc8100c7c8d845b771f Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Wed, 19 Oct 2022 19:44:17 -0400 Subject: [PATCH 1/4] Provide better type hints for a variety of generic types * Makes better documentation * tuple, dict, list, set, function --- CMakeLists.txt | 3 +- docs/advanced/misc.rst | 29 ++++++++ include/pybind11/typing.h | 94 ++++++++++++++++++++++++ tests/extra_python_package/test_files.py | 1 + tests/test_pytypes.cpp | 9 +++ tests/test_pytypes.py | 35 +++++++++ 6 files changed, 170 insertions(+), 1 deletion(-) create mode 100644 include/pybind11/typing.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 87ec103468..15fa799086 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -141,7 +141,8 @@ set(PYBIND11_HEADERS include/pybind11/stl.h include/pybind11/stl_bind.h include/pybind11/stl/filesystem.h - include/pybind11/type_caster_pyobject_ptr.h) + include/pybind11/type_caster_pyobject_ptr.h + include/pybind11/typing.h) # Compare with grep and warn if mismatched if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index 805ec838fc..e9d3ba3d76 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -398,3 +398,32 @@ before they are used as a parameter or return type of a function: pyFoo.def(py::init()); pyBar.def(py::init()); } + +Setting inner type hints in docstrings +====================================== + +When you use pybind11 wrappers for ``list``, ``dict``, and other generic python +types, the docstring will just display the generic type. You can convey the +inner types in the docstring by using a special 'typed' version of the generic +type. + +.. code-block:: cpp + + PYBIND11_MODULE(example, m) { + m.def("pass_list_of_str", [](py::List arg) { + // arg can be used just like py::list + )); + } + +The resulting docstring will be ``pass_list_of_str(arg0: List[str]) -> None``. + +The following special types are available in ``pybind11/typing.h``: + +* ``py::Tuple`` +* ``py::Dict`` +* ``py::List`` +* ``py::Set`` +* ``py::Callable`` + +.. warning:: Just like in python, these are merely hints. They don't actually + enforce the types of their contents at runtime or compile time. diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h new file mode 100644 index 0000000000..019333aa5f --- /dev/null +++ b/include/pybind11/typing.h @@ -0,0 +1,94 @@ +/* + pybind11/typing.h: Convenience wrapper classes for basic Python types + with more explicit annotations. + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#pragma once + +#include "detail/common.h" +#include "cast.h" +#include "pytypes.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +/* + The following types can be used to direct pybind11-generated docstrings + to have have more explicit types (e.g., `List[str]` instead of `list`). + Just use these in place of existing types. + + There is no additional enforcement of types at runtime. +*/ + +template +class Tuple : public tuple { + using tuple::tuple; +}; + +template +class Dict : public dict { + using dict::dict; +}; + +template +class List : public list { + using list::list; +}; + +template +class Set : public set { + using set::set; +}; + +template +class Callable; + +template +class Callable : public function { + using function::function; +}; + +PYBIND11_NAMESPACE_BEGIN(detail) + +template +struct handle_type_name> { + static constexpr auto name + = const_name("Tuple[") + concat(make_caster::name...) + const_name("]"); +}; + +template <> +struct handle_type_name> { + // PEP 484 specifies this syntax for an empty tuple + static constexpr auto name = const_name("Tuple[()]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("Dict[") + make_caster::name + const_name(", ") + + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("List[") + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + static constexpr auto name = const_name("Set[") + make_caster::name + const_name("]"); +}; + +template +struct handle_type_name> { + using retval_type = conditional_t::value, void_type, Return>; + static constexpr auto name = const_name("Callable[[") + concat(make_caster::name...) + + const_name("], ") + make_caster::name + + const_name("]"); +}; + +PYBIND11_NAMESPACE_END(detail) +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index 57387dd8bc..e3d881c0b3 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -44,6 +44,7 @@ "include/pybind11/stl.h", "include/pybind11/stl_bind.h", "include/pybind11/type_caster_pyobject_ptr.h", + "include/pybind11/typing.h", } detail_headers = { diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index b4ee642891..0183f6470d 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -7,6 +7,8 @@ BSD-style license that can be found in the LICENSE file. */ +#include + #include "pybind11_tests.h" #include @@ -820,4 +822,11 @@ TEST_SUBMODULE(pytypes, m) { a >>= b; return a; }); + + m.def("annotate_tuple_float_str", [](const py::Tuple &) {}); + m.def("annotate_tuple_empty", [](const py::Tuple<> &) {}); + m.def("annotate_dict_str_int", [](const py::Dict &) {}); + m.def("annotate_list_int", [](const py::List &) {}); + m.def("annotate_set_str", [](const py::Set &) {}); + m.def("annotate_fn", [](const py::Callable, py::str)> &) {}); } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index eda7a20a9d..9cdd37a6fb 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -896,3 +896,38 @@ def test_inplace_lshift(a, b): def test_inplace_rshift(a, b): expected = a >> b assert m.inplace_rshift(a, b) == expected + + +def test_tuple_nonempty_annotations(doc): + assert ( + doc(m.annotate_tuple_float_str) + == "annotate_tuple_float_str(arg0: Tuple[float, str]) -> None" + ) + + +def test_tuple_empty_annotations(doc): + assert ( + doc(m.annotate_tuple_empty) == "annotate_tuple_empty(arg0: Tuple[()]) -> None" + ) + + +def test_dict_annotations(doc): + assert ( + doc(m.annotate_dict_str_int) + == "annotate_dict_str_int(arg0: Dict[str, int]) -> None" + ) + + +def test_list_annotations(doc): + assert doc(m.annotate_list_int) == "annotate_list_int(arg0: List[int]) -> None" + + +def test_set_annotations(doc): + assert doc(m.annotate_set_str) == "annotate_set_str(arg0: Set[str]) -> None" + + +def test_fn_annotations(doc): + assert ( + doc(m.annotate_fn) + == "annotate_fn(arg0: Callable[[List[str], str], int]) -> None" + ) From 5f8b132771ccbb0ee19ed068b619d3bb54f80f70 Mon Sep 17 00:00:00 2001 From: Dustin Spicuzza Date: Fri, 21 Jul 2023 16:14:17 -0400 Subject: [PATCH 2/4] Move to py::typing --- docs/advanced/misc.rst | 4 ++-- include/pybind11/typing.h | 27 +++++++++++++++------------ tests/test_pytypes.cpp | 12 ++++++------ tests/test_pytypes.py | 12 ++++++------ 4 files changed, 29 insertions(+), 26 deletions(-) diff --git a/docs/advanced/misc.rst b/docs/advanced/misc.rst index e9d3ba3d76..ddd7f39370 100644 --- a/docs/advanced/misc.rst +++ b/docs/advanced/misc.rst @@ -410,12 +410,12 @@ type. .. code-block:: cpp PYBIND11_MODULE(example, m) { - m.def("pass_list_of_str", [](py::List arg) { + m.def("pass_list_of_str", [](py::typing::List arg) { // arg can be used just like py::list )); } -The resulting docstring will be ``pass_list_of_str(arg0: List[str]) -> None``. +The resulting docstring will be ``pass_list_of_str(arg0: list[str]) -> None``. The following special types are available in ``pybind11/typing.h``: diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index 019333aa5f..eb88f5074a 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -15,10 +15,11 @@ #include "pytypes.h" PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) +PYBIND11_NAMESPACE_BEGIN(typing) /* The following types can be used to direct pybind11-generated docstrings - to have have more explicit types (e.g., `List[str]` instead of `list`). + to have have more explicit types (e.g., `list[str]` instead of `list`). Just use these in place of existing types. There is no additional enforcement of types at runtime. @@ -52,38 +53,40 @@ class Callable : public function { using function::function; }; +PYBIND11_NAMESPACE_END(typing) + PYBIND11_NAMESPACE_BEGIN(detail) template -struct handle_type_name> { +struct handle_type_name> { static constexpr auto name - = const_name("Tuple[") + concat(make_caster::name...) + const_name("]"); + = const_name("tuple[") + concat(make_caster::name...) + const_name("]"); }; template <> -struct handle_type_name> { +struct handle_type_name> { // PEP 484 specifies this syntax for an empty tuple - static constexpr auto name = const_name("Tuple[()]"); + static constexpr auto name = const_name("tuple[()]"); }; template -struct handle_type_name> { - static constexpr auto name = const_name("Dict[") + make_caster::name + const_name(", ") +struct handle_type_name> { + static constexpr auto name = const_name("dict[") + make_caster::name + const_name(", ") + make_caster::name + const_name("]"); }; template -struct handle_type_name> { - static constexpr auto name = const_name("List[") + make_caster::name + const_name("]"); +struct handle_type_name> { + static constexpr auto name = const_name("list[") + make_caster::name + const_name("]"); }; template -struct handle_type_name> { - static constexpr auto name = const_name("Set[") + make_caster::name + const_name("]"); +struct handle_type_name> { + static constexpr auto name = const_name("set[") + make_caster::name + const_name("]"); }; template -struct handle_type_name> { +struct handle_type_name> { using retval_type = conditional_t::value, void_type, Return>; static constexpr auto name = const_name("Callable[[") + concat(make_caster::name...) + const_name("], ") + make_caster::name diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 0183f6470d..db621516e9 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -823,10 +823,10 @@ TEST_SUBMODULE(pytypes, m) { return a; }); - m.def("annotate_tuple_float_str", [](const py::Tuple &) {}); - m.def("annotate_tuple_empty", [](const py::Tuple<> &) {}); - m.def("annotate_dict_str_int", [](const py::Dict &) {}); - m.def("annotate_list_int", [](const py::List &) {}); - m.def("annotate_set_str", [](const py::Set &) {}); - m.def("annotate_fn", [](const py::Callable, py::str)> &) {}); + m.def("annotate_tuple_float_str", [](const py::typing::Tuple &) {}); + m.def("annotate_tuple_empty", [](const py::typing::Tuple<> &) {}); + m.def("annotate_dict_str_int", [](const py::typing::Dict &) {}); + m.def("annotate_list_int", [](const py::typing::List &) {}); + m.def("annotate_set_str", [](const py::typing::Set &) {}); + m.def("annotate_fn", [](const py::typing::Callable, py::str)> &) {}); } diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 9cdd37a6fb..e88a9328f7 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -901,33 +901,33 @@ def test_inplace_rshift(a, b): def test_tuple_nonempty_annotations(doc): assert ( doc(m.annotate_tuple_float_str) - == "annotate_tuple_float_str(arg0: Tuple[float, str]) -> None" + == "annotate_tuple_float_str(arg0: tuple[float, str]) -> None" ) def test_tuple_empty_annotations(doc): assert ( - doc(m.annotate_tuple_empty) == "annotate_tuple_empty(arg0: Tuple[()]) -> None" + doc(m.annotate_tuple_empty) == "annotate_tuple_empty(arg0: tuple[()]) -> None" ) def test_dict_annotations(doc): assert ( doc(m.annotate_dict_str_int) - == "annotate_dict_str_int(arg0: Dict[str, int]) -> None" + == "annotate_dict_str_int(arg0: dict[str, int]) -> None" ) def test_list_annotations(doc): - assert doc(m.annotate_list_int) == "annotate_list_int(arg0: List[int]) -> None" + assert doc(m.annotate_list_int) == "annotate_list_int(arg0: list[int]) -> None" def test_set_annotations(doc): - assert doc(m.annotate_set_str) == "annotate_set_str(arg0: Set[str]) -> None" + assert doc(m.annotate_set_str) == "annotate_set_str(arg0: set[str]) -> None" def test_fn_annotations(doc): assert ( doc(m.annotate_fn) - == "annotate_fn(arg0: Callable[[List[str], str], int]) -> None" + == "annotate_fn(arg0: Callable[[list[str], str], int]) -> None" ) From 16857588b9ab685f6adf39d8dc421af478088daa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 21 Jul 2023 20:27:16 +0000 Subject: [PATCH 3/4] style: pre-commit fixes --- tests/test_pytypes.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index db621516e9..cb2f76c031 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -828,5 +828,6 @@ TEST_SUBMODULE(pytypes, m) { m.def("annotate_dict_str_int", [](const py::typing::Dict &) {}); m.def("annotate_list_int", [](const py::typing::List &) {}); m.def("annotate_set_str", [](const py::typing::Set &) {}); - m.def("annotate_fn", [](const py::typing::Callable, py::str)> &) {}); + m.def("annotate_fn", + [](const py::typing::Callable, py::str)> &) {}); } From 072049a0092e95a7dd32d002d164af35e72eaebd Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Thu, 3 Aug 2023 22:08:50 -0700 Subject: [PATCH 4/4] Update copyright line with correct year and actual author. The author information was copy-pasted from the git log output. --- include/pybind11/typing.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index eb88f5074a..74fd82eace 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -2,7 +2,7 @@ pybind11/typing.h: Convenience wrapper classes for basic Python types with more explicit annotations. - Copyright (c) 2016 Wenzel Jakob + Copyright (c) 2023 Dustin Spicuzza All rights reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.