diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 46e88132c..ee8bdd870 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -404,54 +404,55 @@ jobs: # run: cmake --build build --target test_cmake_build - # Testing on CentOS 7 + PGI compilers, which seems to require more workarounds - centos-nvhpc7: - if: ${{ false }} # JOB DISABLED (NEEDS WORK): https://github.com/pybind/pybind11/issues/4690 - runs-on: ubuntu-latest - name: "🐍 3 • CentOS7 / PGI 22.9 • x64" - container: centos:7 + # Testing on Ubuntu + NVHPC (previous PGI) compilers, which seems to require more workarounds + ubuntu-nvhpc7: + runs-on: ubuntu-20.04 + name: "🐍 3 • NVHPC 23.5 • C++17 • x64" + env: + # tzdata will try to ask for the timezone, so set the DEBIAN_FRONTEND + DEBIAN_FRONTEND: 'noninteractive' steps: - uses: actions/checkout@v3 - - name: Add Python 3 and a few requirements - run: yum update -y && yum install -y epel-release && yum install -y git python3-devel make environment-modules cmake3 yum-utils + - name: Add NVHPC Repo + run: | + echo 'deb [trusted=yes] https://developer.download.nvidia.com/hpc-sdk/ubuntu/amd64 /' | \ + sudo tee /etc/apt/sources.list.d/nvhpc.list - - name: Install NVidia HPC SDK - run: yum-config-manager --add-repo https://developer.download.nvidia.com/hpc-sdk/rhel/nvhpc.repo && yum -y install nvhpc-22.9 + - name: Install 🐍 3 & NVHPC + run: | + sudo apt-get update -y && \ + sudo apt-get install -y cmake environment-modules git python3-dev python3-pip python3-numpy && \ + sudo apt-get install -y --no-install-recommends nvhpc-23-5 && \ + sudo rm -rf /var/lib/apt/lists/* + python3 -m pip install --upgrade pip + python3 -m pip install --upgrade pytest - # On CentOS 7, we have to filter a few tests (compiler internal error) - # and allow deeper template recursion (not needed on CentOS 8 with a newer - # standard library). On some systems, you many need further workarounds: + # On some systems, you many need further workarounds: # https://github.com/pybind/pybind11/pull/2475 - name: Configure shell: bash run: | source /etc/profile.d/modules.sh - module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/22.9 - cmake3 -S . -B build -DDOWNLOAD_CATCH=ON \ - -DCMAKE_CXX_STANDARD=11 \ + module load /opt/nvidia/hpc_sdk/modulefiles/nvhpc/23.5 + cmake -S . -B build -DDOWNLOAD_CATCH=ON \ + -DCMAKE_CXX_STANDARD=17 \ -DPYTHON_EXECUTABLE=$(python3 -c "import sys; print(sys.executable)") \ -DCMAKE_CXX_FLAGS="-Wc,--pending_instantiations=0" \ -DPYBIND11_TEST_FILTER="test_smart_ptr.cpp" - # Building before installing Pip should produce a warning but not an error - name: Build - run: cmake3 --build build -j 2 --verbose - - - name: Install CMake with pip - run: | - python3 -m pip install --upgrade pip - python3 -m pip install pytest + run: cmake --build build -j 2 --verbose - name: Python tests - run: cmake3 --build build --target pytest + run: cmake --build build --target pytest - name: C++ tests - run: cmake3 --build build --target cpptest + run: cmake --build build --target cpptest - name: Interface test - run: cmake3 --build build --target test_cmake_build + run: cmake --build build --target test_cmake_build # Testing on GCC using the GCC docker images (only recent images supported) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 86ac965d9..c7f596342 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,13 +32,13 @@ repos: # Black, the code formatter, natively supports pre-commit - repo: https://github.com/psf/black - rev: "23.3.0" # Keep in sync with blacken-docs + rev: "23.7.0" # Keep in sync with blacken-docs hooks: - id: black # Ruff, the Python auto-correcting linter written in Rust - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.0.276 + rev: v0.0.281 hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -84,7 +84,7 @@ repos: # Also code format the docs - repo: https://github.com/asottile/blacken-docs - rev: "1.14.0" + rev: "1.15.0" hooks: - id: blacken-docs additional_dependencies: diff --git a/CMakeLists.txt b/CMakeLists.txt index 87ec10346..15fa79908 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 805ec838f..ddd7f3937 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::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 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/docs/compiling.rst b/docs/compiling.rst index 1fd098bec..9d53904c4 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -143,7 +143,7 @@ Your ``pyproject.toml`` file will likely look something like this: .. code-block:: toml [build-system] - requires = ["setuptools>=42", "wheel", "pybind11~=2.6.1"] + requires = ["setuptools>=42", "pybind11>=2.6.1"] build-backend = "setuptools.build_meta" .. note:: diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h new file mode 100644 index 000000000..74fd82eac --- /dev/null +++ b/include/pybind11/typing.h @@ -0,0 +1,97 @@ +/* + pybind11/typing.h: Convenience wrapper classes for basic Python types + with more explicit annotations. + + 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. +*/ + +#pragma once + +#include "detail/common.h" +#include "cast.h" +#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`). + 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_END(typing) + +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 57387dd8b..e3d881c0b 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -44,6 +44,7 @@ main_headers = { "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 b4ee64289..cb2f76c03 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,12 @@ TEST_SUBMODULE(pytypes, m) { a >>= b; return a; }); + + 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 eda7a20a9..e88a9328f 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" + )