From 976fea05a36585958d067a26092b48288a2af977 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20Gr=C3=BCninger?= Date: Sat, 30 Dec 2023 06:59:47 +0100 Subject: [PATCH 01/17] Fix Clazy warnings (#4988) * stl.h: Use C++11 range-loops with const reference This saves copy-ctor and dtor for non-trivial types by value Found by clazy (range-loop-reference) * test_smart_ptr.cpp cleanup Introduce `pointer_set` https://github.com/boostorg/unordered/issues/139 > Based on the standard, the first move should leave a in a "valid but unspecified state"; --------- Co-authored-by: Ralf W. Grosse-Kunstleve --- include/pybind11/stl.h | 4 ++-- tests/test_smart_ptr.cpp | 27 +++++++++++++++------------ 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/include/pybind11/stl.h b/include/pybind11/stl.h index 6eb485991..f0334b8f8 100644 --- a/include/pybind11/stl.h +++ b/include/pybind11/stl.h @@ -172,7 +172,7 @@ struct list_caster { auto s = reinterpret_borrow(src); value.clear(); reserve_maybe(s, &value); - for (auto it : s) { + for (const auto &it : s) { value_conv conv; if (!conv.load(it, convert)) { return false; @@ -247,7 +247,7 @@ public: return false; } size_t ctr = 0; - for (auto it : l) { + for (const auto &it : l) { value_conv conv; if (!conv.load(it, convert)) { return false; diff --git a/tests/test_smart_ptr.cpp b/tests/test_smart_ptr.cpp index 6d9efcedc..496073b3c 100644 --- a/tests/test_smart_ptr.cpp +++ b/tests/test_smart_ptr.cpp @@ -103,21 +103,26 @@ private: int value; }; +template +std::unordered_set &pointer_set() { + // https://google.github.io/styleguide/cppguide.html#Static_and_Global_Variables + static auto singleton = new std::unordered_set(); + return *singleton; +} + // test_unique_nodelete // Object with a private destructor -class MyObject4; -std::unordered_set myobject4_instances; class MyObject4 { public: explicit MyObject4(int value) : value{value} { print_created(this); - myobject4_instances.insert(this); + pointer_set().insert(this); } int value; static void cleanupAllInstances() { - auto tmp = std::move(myobject4_instances); - myobject4_instances.clear(); + auto tmp = std::move(pointer_set()); + pointer_set().clear(); for (auto *o : tmp) { delete o; } @@ -125,7 +130,7 @@ public: private: ~MyObject4() { - myobject4_instances.erase(this); + pointer_set().erase(this); print_destroyed(this); } }; @@ -133,19 +138,17 @@ private: // test_unique_deleter // Object with std::unique_ptr where D is not matching the base class // Object with a protected destructor -class MyObject4a; -std::unordered_set myobject4a_instances; class MyObject4a { public: explicit MyObject4a(int i) : value{i} { print_created(this); - myobject4a_instances.insert(this); + pointer_set().insert(this); }; int value; static void cleanupAllInstances() { - auto tmp = std::move(myobject4a_instances); - myobject4a_instances.clear(); + auto tmp = std::move(pointer_set()); + pointer_set().clear(); for (auto *o : tmp) { delete o; } @@ -153,7 +156,7 @@ public: protected: virtual ~MyObject4a() { - myobject4a_instances.erase(this); + pointer_set().erase(this); print_destroyed(this); } }; From b583336cf762e24c81912aab2c3f74f14396e384 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 18:51:02 -0800 Subject: [PATCH 02/17] chore(deps): bump ilammy/msvc-dev-cmd from 1.12.1 to 1.13.0 (#4995) Bumps [ilammy/msvc-dev-cmd](https://github.com/ilammy/msvc-dev-cmd) from 1.12.1 to 1.13.0. - [Release notes](https://github.com/ilammy/msvc-dev-cmd/releases) - [Commits](https://github.com/ilammy/msvc-dev-cmd/compare/v1.12.1...v1.13.0) --- updated-dependencies: - dependency-name: ilammy/msvc-dev-cmd dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7aead4570..9d4c8e1f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -800,7 +800,7 @@ jobs: uses: jwlawson/actions-setup-cmake@v1.14 - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.12.1 + uses: ilammy/msvc-dev-cmd@v1.13.0 with: arch: x86 @@ -853,7 +853,7 @@ jobs: uses: jwlawson/actions-setup-cmake@v1.14 - name: Prepare MSVC - uses: ilammy/msvc-dev-cmd@v1.12.1 + uses: ilammy/msvc-dev-cmd@v1.13.0 with: arch: x86 From f29def9ea467c7fde754440733047953fa4b990c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 1 Jan 2024 19:25:58 -0800 Subject: [PATCH 03/17] chore(deps): update pre-commit hooks (#4994) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.6 → v0.1.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.6...v0.1.9) - [github.com/pre-commit/mirrors-mypy: v1.7.1 → v1.8.0](https://github.com/pre-commit/mirrors-mypy/compare/v1.7.1...v1.8.0) - [github.com/PyCQA/pylint: v3.0.1 → v3.0.3](https://github.com/PyCQA/pylint/compare/v3.0.1...v3.0.3) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 53f1a5ea1..cb28515d8 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,7 +32,7 @@ repos: # Ruff, the Python auto-correcting linter/formatter written in Rust - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.6 + rev: v0.1.9 hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -40,7 +40,7 @@ repos: # Check static types with mypy - repo: https://github.com/pre-commit/mirrors-mypy - rev: "v1.7.1" + rev: "v1.8.0" hooks: - id: mypy args: [] @@ -142,7 +142,7 @@ repos: # PyLint has native support - not always usable, but works for us - repo: https://github.com/PyCQA/pylint - rev: "v3.0.1" + rev: "v3.0.3" hooks: - id: pylint files: ^pybind11 From aec6cc5406edb076f5a489c2d7f84bb07052c4a3 Mon Sep 17 00:00:00 2001 From: Ilya Lavrenov Date: Mon, 8 Jan 2024 17:40:41 +0400 Subject: [PATCH 04/17] fix(cmake): skip empty PYBIND11_PYTHON_EXECUTABLE_LAST for the first cmake run (#4856) * fix(cmake): skip empty PYBIND11_PYTHON_EXECUTABLE_LAST for the first cmake run * style: pre-commit fixes * Update pybind11NewTools.cmake * style: pre-commit fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: Henry Schreiner --- tools/pybind11NewTools.cmake | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/tools/pybind11NewTools.cmake b/tools/pybind11NewTools.cmake index cd88a6450..9fe2eb08d 100644 --- a/tools/pybind11NewTools.cmake +++ b/tools/pybind11NewTools.cmake @@ -110,15 +110,17 @@ if(NOT DEFINED ${_Python}_EXECUTABLE) endif() -if(NOT ${_Python}_EXECUTABLE STREQUAL PYBIND11_PYTHON_EXECUTABLE_LAST) +if(DEFINED PYBIND11_PYTHON_EXECUTABLE_LAST AND NOT ${_Python}_EXECUTABLE STREQUAL + PYBIND11_PYTHON_EXECUTABLE_LAST) # Detect changes to the Python version/binary in subsequent CMake runs, and refresh config if needed unset(PYTHON_IS_DEBUG CACHE) unset(PYTHON_MODULE_EXTENSION CACHE) - set(PYBIND11_PYTHON_EXECUTABLE_LAST - "${${_Python}_EXECUTABLE}" - CACHE INTERNAL "Python executable during the last CMake run") endif() +set(PYBIND11_PYTHON_EXECUTABLE_LAST + "${${_Python}_EXECUTABLE}" + CACHE INTERNAL "Python executable during the last CMake run") + if(NOT DEFINED PYTHON_IS_DEBUG) # Debug check - see https://stackoverflow.com/questions/646518/python-how-to-detect-debug-Interpreter execute_process( From 31b7e14052b547a5ddf97c07840a830ec27524b7 Mon Sep 17 00:00:00 2001 From: Huanchen Zhai <44282896+hczhai@users.noreply.github.com> Date: Sat, 13 Jan 2024 11:07:36 -0800 Subject: [PATCH 05/17] bugfix: removing typing and duplicate ``class_`` for KeysView/ValuesView/ItemsView. Fix #4529 (#4985) * remove typing for KeysView/ValuesView/ItemsView * add tests for map view types --- include/pybind11/stl_bind.h | 79 ++++++++++++------------------------- tests/test_stl_binders.cpp | 10 +++++ tests/test_stl_binders.py | 30 ++++++++++++-- 3 files changed, 62 insertions(+), 57 deletions(-) diff --git a/include/pybind11/stl_bind.h b/include/pybind11/stl_bind.h index 4d0854f6a..a226cbc0e 100644 --- a/include/pybind11/stl_bind.h +++ b/include/pybind11/stl_bind.h @@ -645,49 +645,50 @@ auto map_if_insertion_operator(Class_ &cl, std::string const &name) "Return the canonical string representation of this map."); } -template struct keys_view { virtual size_t len() = 0; virtual iterator iter() = 0; - virtual bool contains(const KeyType &k) = 0; - virtual bool contains(const object &k) = 0; + virtual bool contains(const handle &k) = 0; virtual ~keys_view() = default; }; -template struct values_view { virtual size_t len() = 0; virtual iterator iter() = 0; virtual ~values_view() = default; }; -template struct items_view { virtual size_t len() = 0; virtual iterator iter() = 0; virtual ~items_view() = default; }; -template -struct KeysViewImpl : public KeysView { +template +struct KeysViewImpl : public detail::keys_view { explicit KeysViewImpl(Map &map) : map(map) {} size_t len() override { return map.size(); } iterator iter() override { return make_key_iterator(map.begin(), map.end()); } - bool contains(const typename Map::key_type &k) override { return map.find(k) != map.end(); } - bool contains(const object &) override { return false; } + bool contains(const handle &k) override { + try { + return map.find(k.template cast()) != map.end(); + } catch (const cast_error &) { + return false; + } + } Map ↦ }; -template -struct ValuesViewImpl : public ValuesView { +template +struct ValuesViewImpl : public detail::values_view { explicit ValuesViewImpl(Map &map) : map(map) {} size_t len() override { return map.size(); } iterator iter() override { return make_value_iterator(map.begin(), map.end()); } Map ↦ }; -template -struct ItemsViewImpl : public ItemsView { +template +struct ItemsViewImpl : public detail::items_view { explicit ItemsViewImpl(Map &map) : map(map) {} size_t len() override { return map.size(); } iterator iter() override { return make_iterator(map.begin(), map.end()); } @@ -700,11 +701,9 @@ template , typename... class_ bind_map(handle scope, const std::string &name, Args &&...args) { using KeyType = typename Map::key_type; using MappedType = typename Map::mapped_type; - using StrippedKeyType = detail::remove_cvref_t; - using StrippedMappedType = detail::remove_cvref_t; - using KeysView = detail::keys_view; - using ValuesView = detail::values_view; - using ItemsView = detail::items_view; + using KeysView = detail::keys_view; + using ValuesView = detail::values_view; + using ItemsView = detail::items_view; using Class_ = class_; // If either type is a non-module-local bound type then make the map binding non-local as well; @@ -718,39 +717,20 @@ class_ bind_map(handle scope, const std::string &name, Args && } Class_ cl(scope, name.c_str(), pybind11::module_local(local), std::forward(args)...); - static constexpr auto key_type_descr = detail::make_caster::name; - static constexpr auto mapped_type_descr = detail::make_caster::name; - std::string key_type_name(key_type_descr.text), mapped_type_name(mapped_type_descr.text); - // If key type isn't properly wrapped, fall back to C++ names - if (key_type_name == "%") { - key_type_name = detail::type_info_description(typeid(KeyType)); - } - // Similarly for value type: - if (mapped_type_name == "%") { - mapped_type_name = detail::type_info_description(typeid(MappedType)); - } - - // Wrap KeysView[KeyType] if it wasn't already wrapped + // Wrap KeysView if it wasn't already wrapped if (!detail::get_type_info(typeid(KeysView))) { - class_ keys_view( - scope, ("KeysView[" + key_type_name + "]").c_str(), pybind11::module_local(local)); + class_ keys_view(scope, "KeysView", pybind11::module_local(local)); keys_view.def("__len__", &KeysView::len); keys_view.def("__iter__", &KeysView::iter, keep_alive<0, 1>() /* Essential: keep view alive while iterator exists */ ); - keys_view.def("__contains__", - static_cast(&KeysView::contains)); - // Fallback for when the object is not of the key type - keys_view.def("__contains__", - static_cast(&KeysView::contains)); + keys_view.def("__contains__", &KeysView::contains); } // Similarly for ValuesView: if (!detail::get_type_info(typeid(ValuesView))) { - class_ values_view(scope, - ("ValuesView[" + mapped_type_name + "]").c_str(), - pybind11::module_local(local)); + class_ values_view(scope, "ValuesView", pybind11::module_local(local)); values_view.def("__len__", &ValuesView::len); values_view.def("__iter__", &ValuesView::iter, @@ -759,10 +739,7 @@ class_ bind_map(handle scope, const std::string &name, Args && } // Similarly for ItemsView: if (!detail::get_type_info(typeid(ItemsView))) { - class_ items_view( - scope, - ("ItemsView[" + key_type_name + ", ").append(mapped_type_name + "]").c_str(), - pybind11::module_local(local)); + class_ items_view(scope, "ItemsView", pybind11::module_local(local)); items_view.def("__len__", &ItemsView::len); items_view.def("__iter__", &ItemsView::iter, @@ -788,25 +765,19 @@ class_ bind_map(handle scope, const std::string &name, Args && cl.def( "keys", - [](Map &m) { - return std::unique_ptr(new detail::KeysViewImpl(m)); - }, + [](Map &m) { return std::unique_ptr(new detail::KeysViewImpl(m)); }, keep_alive<0, 1>() /* Essential: keep map alive while view exists */ ); cl.def( "values", - [](Map &m) { - return std::unique_ptr(new detail::ValuesViewImpl(m)); - }, + [](Map &m) { return std::unique_ptr(new detail::ValuesViewImpl(m)); }, keep_alive<0, 1>() /* Essential: keep map alive while view exists */ ); cl.def( "items", - [](Map &m) { - return std::unique_ptr(new detail::ItemsViewImpl(m)); - }, + [](Map &m) { return std::unique_ptr(new detail::ItemsViewImpl(m)); }, keep_alive<0, 1>() /* Essential: keep map alive while view exists */ ); diff --git a/tests/test_stl_binders.cpp b/tests/test_stl_binders.cpp index e52a03b6d..96af9a4c4 100644 --- a/tests/test_stl_binders.cpp +++ b/tests/test_stl_binders.cpp @@ -192,6 +192,16 @@ TEST_SUBMODULE(stl_binders, m) { py::bind_map>(m, "UnorderedMapStringDoubleConst"); + // test_map_view_types + py::bind_map>(m, "MapStringFloat"); + py::bind_map>(m, "UnorderedMapStringFloat"); + + py::bind_map, int32_t>>(m, "MapPairDoubleIntInt32"); + py::bind_map, int64_t>>(m, "MapPairDoubleIntInt64"); + + py::bind_map>(m, "MapIntObject"); + py::bind_map>(m, "MapStringObject"); + py::class_(m, "ENC").def(py::init()).def_readwrite("value", &E_nc::value); // test_noncopyable_containers diff --git a/tests/test_stl_binders.py b/tests/test_stl_binders.py index c00d45b92..d1bf64aa0 100644 --- a/tests/test_stl_binders.py +++ b/tests/test_stl_binders.py @@ -317,9 +317,9 @@ def test_map_view_types(): map_string_double_const = m.MapStringDoubleConst() unordered_map_string_double_const = m.UnorderedMapStringDoubleConst() - assert map_string_double.keys().__class__.__name__ == "KeysView[str]" - assert map_string_double.values().__class__.__name__ == "ValuesView[float]" - assert map_string_double.items().__class__.__name__ == "ItemsView[str, float]" + assert map_string_double.keys().__class__.__name__ == "KeysView" + assert map_string_double.values().__class__.__name__ == "ValuesView" + assert map_string_double.items().__class__.__name__ == "ItemsView" keys_type = type(map_string_double.keys()) assert type(unordered_map_string_double.keys()) is keys_type @@ -336,6 +336,30 @@ def test_map_view_types(): assert type(map_string_double_const.items()) is items_type assert type(unordered_map_string_double_const.items()) is items_type + map_string_float = m.MapStringFloat() + unordered_map_string_float = m.UnorderedMapStringFloat() + + assert type(map_string_float.keys()) is keys_type + assert type(unordered_map_string_float.keys()) is keys_type + assert type(map_string_float.values()) is values_type + assert type(unordered_map_string_float.values()) is values_type + assert type(map_string_float.items()) is items_type + assert type(unordered_map_string_float.items()) is items_type + + map_pair_double_int_int32 = m.MapPairDoubleIntInt32() + map_pair_double_int_int64 = m.MapPairDoubleIntInt64() + + assert type(map_pair_double_int_int32.values()) is values_type + assert type(map_pair_double_int_int64.values()) is values_type + + map_int_object = m.MapIntObject() + map_string_object = m.MapStringObject() + + assert type(map_int_object.keys()) is keys_type + assert type(map_string_object.keys()) is keys_type + assert type(map_int_object.items()) is items_type + assert type(map_string_object.items()) is items_type + def test_recursive_vector(): recursive_vector = m.RecursiveVector() From 39e65e10d05bfdc80413a6290bdae3391f0f93d7 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Sat, 13 Jan 2024 15:28:39 -0500 Subject: [PATCH 06/17] ci: group dependabot updates (#4986) --- .github/dependabot.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 2c7d17083..6c4b36953 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,4 +4,8 @@ updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "daily" + interval: "weekly" + groups: + actions: + patterns: + - "*" From 869cc1ff085dd405635b00eb46e5c84f50f26099 Mon Sep 17 00:00:00 2001 From: "Ralf W. Grosse-Kunstleve" Date: Tue, 16 Jan 2024 21:09:20 -0800 Subject: [PATCH 07/17] install mingw-w64-${{matrix.env}}-python-scipy only for mingw64 (#5006) --- .github/workflows/ci.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d4c8e1f4..9def058f5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -959,7 +959,6 @@ jobs: mingw-w64-${{matrix.env}}-gcc mingw-w64-${{matrix.env}}-python-pip mingw-w64-${{matrix.env}}-python-numpy - mingw-w64-${{matrix.env}}-python-scipy mingw-w64-${{matrix.env}}-cmake mingw-w64-${{matrix.env}}-make mingw-w64-${{matrix.env}}-python-pytest @@ -967,6 +966,14 @@ jobs: mingw-w64-${{matrix.env}}-boost mingw-w64-${{matrix.env}}-catch + - uses: msys2/setup-msys2@v2 + if: matrix.sys == 'mingw64' + with: + msystem: ${{matrix.sys}} + install: >- + git + mingw-w64-${{matrix.env}}-python-scipy + - uses: actions/checkout@v4 - name: Configure C++11 From 768cebe17e65c2a0a64ed067510729efc3c7ff6c Mon Sep 17 00:00:00 2001 From: Pascal Thomet Date: Sun, 28 Jan 2024 18:10:03 +0100 Subject: [PATCH 08/17] doc: add litgen to the automatic generators list (compiling.rst) (#5012) * doc: add litgen to the automatic generators list (compiling.rst) Added this: [litgen]_ is an automatic python bindings generator with a focus on generating documented and discoverable bindings: bindings will nicely reproduce the documentation found in headers. It is is based on srcML (srcml.org), a highly scalable, multi-language parsing tool with a developer centric approach. The API that you want to expose to python must be C++14 compatible (but your implementation can use more modern constructs). .. [litgen] https://pthom.github.io/litgen * style: pre-commit fixes --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- docs/compiling.rst | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/compiling.rst b/docs/compiling.rst index f6a21691d..3be84ba7d 100644 --- a/docs/compiling.rst +++ b/docs/compiling.rst @@ -639,3 +639,11 @@ cross-project dependency management. Additionally, it is able to autogenerate customizable pybind11-based wrappers by parsing C++ header files. .. [robotpy-build] https://robotpy-build.readthedocs.io + +[litgen]_ is an automatic python bindings generator with a focus on generating +documented and discoverable bindings: bindings will nicely reproduce the documentation +found in headers. It is is based on srcML (srcml.org), a highly scalable, multi-language +parsing tool with a developer centric approach. The API that you want to expose to python +must be C++14 compatible (but your implementation can use more modern constructs). + +.. [litgen] https://pthom.github.io/litgen From 416f7a4410d3638273dd071a7cd4e63cd481ca92 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 5 Feb 2024 23:07:31 -0800 Subject: [PATCH 09/17] chore(deps): update pre-commit hooks (#5018) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.1.9 → v0.2.0](https://github.com/astral-sh/ruff-pre-commit/compare/v0.1.9...v0.2.0) - [github.com/sirosen/texthooks: 0.6.3 → 0.6.4](https://github.com/sirosen/texthooks/compare/0.6.3...0.6.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index cb28515d8..2226f9930 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -32,7 +32,7 @@ repos: # Ruff, the Python auto-correcting linter/formatter written in Rust - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.1.9 + rev: v0.2.0 hooks: - id: ruff args: ["--fix", "--show-fixes"] @@ -93,7 +93,7 @@ repos: # Avoid directional quotes - repo: https://github.com/sirosen/texthooks - rev: "0.6.3" + rev: "0.6.4" hooks: - id: fix-ligatures - id: fix-smartquotes From e84d446d8e8f84bcb009797dd02a3ca4d7f1b999 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 13 Feb 2024 18:29:42 -0500 Subject: [PATCH 10/17] ci: Ignore v1 updates for checkout (#5023) Signed-off-by: Henry Schreiner --- .github/dependabot.yml | 4 ++++ .github/workflows/ci.yml | 2 -- .pre-commit-config.yaml | 7 +++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6c4b36953..02742878d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -9,3 +9,7 @@ updates: actions: patterns: - "*" + ignore: + - dependency-name: actions/checkout + versions: + - "<2" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9def058f5..b9f6403e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -529,8 +529,6 @@ jobs: # Testing on ICC using the oneAPI apt repo icc: runs-on: ubuntu-20.04 - strategy: - fail-fast: false name: "🐍 3 • ICC latest • x64" diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2226f9930..2bb47b21c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -146,3 +146,10 @@ repos: hooks: - id: pylint files: ^pybind11 + +- repo: https://github.com/python-jsonschema/check-jsonschema + rev: 0.28.0 + hooks: + - id: check-readthedocs + - id: check-github-workflows + - id: check-dependabot From 0518bf9f9ad462f964b88b1245886af063b90057 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 13 Feb 2024 18:36:39 -0500 Subject: [PATCH 11/17] ci: ignore actions/checkout until v5 comes out --- .github/dependabot.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 02742878d..22c34bd74 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,4 +12,4 @@ updates: ignore: - dependency-name: actions/checkout versions: - - "<2" + - "<5" From 8b48ff878c168b51fe5ef7b8c728815b9e1a9857 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Feb 2024 20:50:51 -0500 Subject: [PATCH 12/17] chore(deps): bump the actions group with 3 updates (#5024) Bumps the actions group with 3 updates: [jwlawson/actions-setup-cmake](https://github.com/jwlawson/actions-setup-cmake), [actions/cache](https://github.com/actions/cache) and [pre-commit/action](https://github.com/pre-commit/action). Updates `jwlawson/actions-setup-cmake` from 1.14 to 2.0 - [Release notes](https://github.com/jwlawson/actions-setup-cmake/releases) - [Commits](https://github.com/jwlawson/actions-setup-cmake/compare/v1.14...v2.0) Updates `actions/cache` from 3 to 4 - [Release notes](https://github.com/actions/cache/releases) - [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md) - [Commits](https://github.com/actions/cache/compare/v3...v4) Updates `pre-commit/action` from 3.0.0 to 3.0.1 - [Release notes](https://github.com/pre-commit/action/releases) - [Commits](https://github.com/pre-commit/action/compare/v3.0.0...v3.0.1) --- updated-dependencies: - dependency-name: jwlawson/actions-setup-cmake dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major dependency-group: actions - dependency-name: pre-commit/action dependency-type: direct:production update-type: version-update:semver-patch dependency-group: actions ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci.yml | 20 ++++++++++---------- .github/workflows/configure.yml | 2 +- .github/workflows/format.yml | 2 +- .github/workflows/upstream.yml | 2 +- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9f6403e7..cf03eebfd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,11 +86,11 @@ jobs: run: brew install boost - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.14 + uses: jwlawson/actions-setup-cmake@v2.0 - name: Cache wheels if: runner.os == 'macOS' - uses: actions/cache@v3 + uses: actions/cache@v4 with: # This path is specific to macOS - we really only need it for PyPy NumPy wheels # See https://github.com/actions/cache/blob/master/examples.md#python---pip @@ -214,11 +214,11 @@ jobs: debug: ${{ matrix.python-debug }} - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.14 + uses: jwlawson/actions-setup-cmake@v2.0 - name: Valgrind cache if: matrix.valgrind - uses: actions/cache@v3 + uses: actions/cache@v4 id: cache-valgrind with: path: valgrind @@ -484,7 +484,7 @@ jobs: run: python3 -m pip install --upgrade pip - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.14 + uses: jwlawson/actions-setup-cmake@v2.0 - name: Configure shell: bash @@ -795,7 +795,7 @@ jobs: architecture: x86 - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.14 + uses: jwlawson/actions-setup-cmake@v2.0 - name: Prepare MSVC uses: ilammy/msvc-dev-cmd@v1.13.0 @@ -848,7 +848,7 @@ jobs: architecture: x86 - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.14 + uses: jwlawson/actions-setup-cmake@v2.0 - name: Prepare MSVC uses: ilammy/msvc-dev-cmd@v1.13.0 @@ -899,7 +899,7 @@ jobs: python3 -m pip install -r tests/requirements.txt - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.14 + uses: jwlawson/actions-setup-cmake@v2.0 - name: Configure C++20 run: > @@ -1063,7 +1063,7 @@ jobs: python-version: ${{ matrix.python }} - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.14 + uses: jwlawson/actions-setup-cmake@v2.0 - name: Install ninja-build tool uses: seanmiddleditch/gha-setup-ninja@v4 @@ -1133,7 +1133,7 @@ jobs: run: clang++ --version - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.14 + uses: jwlawson/actions-setup-cmake@v2.0 - name: Run pip installs run: | diff --git a/.github/workflows/configure.yml b/.github/workflows/configure.yml index 7fd320fef..dca37864c 100644 --- a/.github/workflows/configure.yml +++ b/.github/workflows/configure.yml @@ -63,7 +63,7 @@ jobs: # An action for adding a specific version of CMake: # https://github.com/jwlawson/actions-setup-cmake - name: Setup CMake ${{ matrix.cmake }} - uses: jwlawson/actions-setup-cmake@v1.14 + uses: jwlawson/actions-setup-cmake@v2.0 with: cmake-version: ${{ matrix.cmake }} diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 2a520d6f4..1eaa56e1c 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -31,7 +31,7 @@ jobs: python-version: "3.x" - name: Add matchers run: echo "::add-matcher::$GITHUB_WORKSPACE/.github/matchers/pylint.json" - - uses: pre-commit/action@v3.0.0 + - uses: pre-commit/action@v3.0.1 with: # Slow hooks are marked with manual - slow is okay here, run them too extra_args: --hook-stage manual --all-files diff --git a/.github/workflows/upstream.yml b/.github/workflows/upstream.yml index 1a5c3e4bd..389260038 100644 --- a/.github/workflows/upstream.yml +++ b/.github/workflows/upstream.yml @@ -36,7 +36,7 @@ jobs: run: sudo apt-get install libboost-dev - name: Update CMake - uses: jwlawson/actions-setup-cmake@v1.14 + uses: jwlawson/actions-setup-cmake@v2.0 - name: Run pip installs run: | From ec73bdaf1fab4f89d0de679e7cc0f4e2b63b20a1 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 21 Mar 2024 02:27:58 -0400 Subject: [PATCH 13/17] ci: skipping test for Windows Clang failure (#5062) * ci: trying things for Windows Clang failure Signed-off-by: Henry Schreiner * WIP: try using older clang Signed-off-by: Henry Schreiner * tests: skip broken test Signed-off-by: Henry Schreiner * tests: try to skip test in tests Signed-off-by: Henry Schreiner * fix(tests): Prefer __version__ over MSVC Signed-off-by: Henry Schreiner * chore: avoid warning on Clang Signed-off-by: Henry Schreiner * Update tests/test_exceptions.py * Update tests/test_exceptions.py --------- Signed-off-by: Henry Schreiner --- include/pybind11/detail/internals.h | 11 ++++++++--- tests/CMakeLists.txt | 6 +++++- tests/pybind11_tests.cpp | 6 +++--- tests/test_exceptions.py | 7 ++++++- 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index ab399016e..c1047e4a0 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -67,9 +67,14 @@ inline PyObject *make_object_base_type(PyTypeObject *metaclass); // `Py_LIMITED_API` anyway. # if PYBIND11_INTERNALS_VERSION > 4 # define PYBIND11_TLS_KEY_REF Py_tss_t & -# if defined(__GNUC__) && !defined(__INTEL_COMPILER) -// Clang on macOS warns due to `Py_tss_NEEDS_INIT` not specifying an initializer -// for every field. +# if defined(__clang__) +# define PYBIND11_TLS_KEY_INIT(var) \ + _Pragma("clang diagnostic push") /**/ \ + _Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \ + Py_tss_t var \ + = Py_tss_NEEDS_INIT; \ + _Pragma("clang diagnostic pop") +# elif defined(__GNUC__) && !defined(__INTEL_COMPILER) # define PYBIND11_TLS_KEY_INIT(var) \ _Pragma("GCC diagnostic push") /**/ \ _Pragma("GCC diagnostic ignored \"-Wmissing-field-initializers\"") /**/ \ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6ad729aae..e347a2e5c 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -520,11 +520,15 @@ set(PYBIND11_TEST_PREFIX_COMMAND "" CACHE STRING "Put this before pytest, use for checkers and such") +set(PYBIND11_PYTEST_ARGS + "" + CACHE STRING "Extra arguments for pytest") + # A single command to compile and run the tests add_custom_target( pytest COMMAND ${PYBIND11_TEST_PREFIX_COMMAND} ${PYTHON_EXECUTABLE} -m pytest - ${PYBIND11_ABS_PYTEST_FILES} + ${PYBIND11_ABS_PYTEST_FILES} ${PYBIND11_PYTEST_ARGS} DEPENDS ${test_targets} WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" USES_TERMINAL) diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp index 624034648..4578e4b7f 100644 --- a/tests/pybind11_tests.cpp +++ b/tests/pybind11_tests.cpp @@ -80,10 +80,10 @@ PYBIND11_MODULE(pybind11_tests, m) { // Intentionally kept minimal to not create a maintenance chore // ("just enough" to be conclusive). -#if defined(_MSC_FULL_VER) - m.attr("compiler_info") = "MSVC " PYBIND11_TOSTRING(_MSC_FULL_VER); -#elif defined(__VERSION__) +#if defined(__VERSION__) m.attr("compiler_info") = __VERSION__; +#elif defined(_MSC_FULL_VER) + m.attr("compiler_info") = "MSVC " PYBIND11_TOSTRING(_MSC_FULL_VER); #else m.attr("compiler_info") = py::none(); #endif diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 5d8e6292a..6752285ac 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -4,7 +4,7 @@ import pytest import env import pybind11_cross_module_tests as cm -import pybind11_tests # noqa: F401 +import pybind11_tests from pybind11_tests import exceptions as m @@ -248,6 +248,11 @@ def test_nested_throws(capture): assert str(excinfo.value) == "this is a helper-defined translated exception" +# TODO: Investigate this crash, see pybind/pybind11#5062 for background +@pytest.mark.skipif( + sys.platform.startswith("win32") and "Clang" in pybind11_tests.compiler_info, + reason="Started segfaulting February 2024", +) def test_throw_nested_exception(): with pytest.raises(RuntimeError) as excinfo: m.throw_nested_exception() From ddb8b67a8aa8198ec8a028fb28f6870e44e7a467 Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Thu, 21 Mar 2024 03:23:57 -0400 Subject: [PATCH 14/17] fix(cmake): allow forcing old FindPython (#5042) --- tools/pybind11Common.cmake | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/tools/pybind11Common.cmake b/tools/pybind11Common.cmake index 50b15e11b..57721aeb1 100644 --- a/tools/pybind11Common.cmake +++ b/tools/pybind11Common.cmake @@ -173,12 +173,16 @@ endif() # Check to see which Python mode we are in, new, old, or no python if(PYBIND11_NOPYTHON) set(_pybind11_nopython ON) + # We won't use new FindPython if PYBIND11_FINDPYTHON is defined and falselike + # Otherwise, we use if FindPythonLibs is missing or if FindPython was already used elseif( - _pybind11_missing_old_python STREQUAL "NEW" - OR PYBIND11_FINDPYTHON - OR Python_FOUND - OR Python2_FOUND - OR Python3_FOUND) + (NOT DEFINED PYBIND11_FINDPYTHON OR PYBIND11_FINDPYTHON) + AND (_pybind11_missing_old_python STREQUAL "NEW" + OR PYBIND11_FINDPYTHON + OR Python_FOUND + OR Python3_FOUND + )) + # New mode include("${CMAKE_CURRENT_LIST_DIR}/pybind11NewTools.cmake") From 65370f330ea9349bd9e996028b4cb7adfde37660 Mon Sep 17 00:00:00 2001 From: Jason Watson Date: Thu, 21 Mar 2024 08:24:19 +0100 Subject: [PATCH 15/17] Create handle_type_name specialization to type-hint variable length tuples (#5051) --- include/pybind11/typing.h | 7 +++++++ tests/test_pytypes.cpp | 2 ++ tests/test_pytypes.py | 7 +++++++ 3 files changed, 16 insertions(+) diff --git a/include/pybind11/typing.h b/include/pybind11/typing.h index b7b1a4e54..0ee329d85 100644 --- a/include/pybind11/typing.h +++ b/include/pybind11/typing.h @@ -79,6 +79,13 @@ struct handle_type_name> { static constexpr auto name = const_name("tuple[()]"); }; +template +struct handle_type_name> { + // PEP 484 specifies this syntax for a variable-length tuple + static constexpr auto name + = const_name("tuple[") + make_caster::name + const_name(", ...]"); +}; + template struct handle_type_name> { static constexpr auto name = const_name("dict[") + make_caster::name + const_name(", ") diff --git a/tests/test_pytypes.cpp b/tests/test_pytypes.cpp index 0cd480ebb..e840eb61f 100644 --- a/tests/test_pytypes.cpp +++ b/tests/test_pytypes.cpp @@ -825,6 +825,8 @@ TEST_SUBMODULE(pytypes, m) { m.def("annotate_tuple_float_str", [](const py::typing::Tuple &) {}); m.def("annotate_tuple_empty", [](const py::typing::Tuple<> &) {}); + m.def("annotate_tuple_variable_length", + [](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 &) {}); diff --git a/tests/test_pytypes.py b/tests/test_pytypes.py index 2b2027316..cfb144523 100644 --- a/tests/test_pytypes.py +++ b/tests/test_pytypes.py @@ -911,6 +911,13 @@ def test_tuple_empty_annotations(doc): ) +def test_tuple_variable_length_annotations(doc): + assert ( + doc(m.annotate_tuple_variable_length) + == "annotate_tuple_variable_length(arg0: tuple[float, ...]) -> None" + ) + + def test_dict_annotations(doc): assert ( doc(m.annotate_dict_str_int) From e0f2c71596dde6303803870c7c0ce13440b57dbe Mon Sep 17 00:00:00 2001 From: Henry Schreiner Date: Tue, 26 Mar 2024 13:57:05 -0400 Subject: [PATCH 16/17] tests: hide warning on clang (#5069) Signed-off-by: Henry Schreiner --- tests/test_constants_and_functions.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_constants_and_functions.cpp b/tests/test_constants_and_functions.cpp index 312edca9e..bf42b4155 100644 --- a/tests/test_constants_and_functions.cpp +++ b/tests/test_constants_and_functions.cpp @@ -54,7 +54,11 @@ int f2(int x) noexcept(true) { return x + 2; } int f3(int x) noexcept(false) { return x + 3; } PYBIND11_WARNING_PUSH PYBIND11_WARNING_DISABLE_GCC("-Wdeprecated") +#if defined(__clang_major__) && __clang_major__ >= 5 +PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated-dynamic-exception-spec") +#else PYBIND11_WARNING_DISABLE_CLANG("-Wdeprecated") +#endif // NOLINTNEXTLINE(modernize-use-noexcept) int f4(int x) throw() { return x + 4; } // Deprecated equivalent to noexcept(true) PYBIND11_WARNING_POP From 705efccecdf8632612dfb538008efa02c862d615 Mon Sep 17 00:00:00 2001 From: Sebastian Berg Date: Tue, 26 Mar 2024 23:20:11 +0100 Subject: [PATCH 17/17] feat: make `numpy.h` compatible with both NumPy 1.x and 2.x (#5050) * API: Make `numpy.h` compatible with both NumPy 1.x and 2.x * TST: Update numpy dtype flags test to not covert flags to char * API: Add `numpy2.h` instead and make `numpy.h` safe This means that users of `numpy.h` cannot be broken, but need to update to `numpy2.h` if they want to compile for NumPy 2. Using Macros simply and didn't bother to try to remove unnecessary code paths. * API: Rather than `numpy2.h` use a define for the user. * Thread `PYBIND11_NUMPY2_SUPPORT` through things and try to adept test matrix * Small fixups (shouldn't matter)? * Fixup. Does upgrading scipy help? (it shouldn't?) (Some other small fixup) * Use NumPy 2 nightlies for ubuntu-latest job also * BUG: Fix numpy.bool check * TST: Fix complexwarning * BUG: Fix the fact that only the 50 slot is filled with the copy alias (There were 3 functions all doing the same, only this slot survived 2.x) * TST: One more test tweak * TST: Use "long" name for long, since it changed on windows * TST: Apparently we didn't always have ulong, so just use `L` * TST: Enforce dtype='l' for test as default isn't long anymore on windows * Rename macro and invert logic to PYBIND11_NUMPY_1_ONLY * PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED * Test and code comment expansion * CI: Use pre-releases of numpy/scipy from pip via explicit version * CI: NumPy 2 only available on almalinux (as it is Python >=3.9) * MAINT: Match name more exactly and adopt error phrasing * MAINT: Pushed early, move helper to be private member * fix error message compilation when using NumPy 1.x-only backcompat * silence name shadowing warning * chore: minor optimization Signed-off-by: Henry Schreiner --------- Signed-off-by: Henry Schreiner Co-authored-by: Ralf W. Grosse-Kunstleve Co-authored-by: Henry Schreiner --- .github/workflows/ci.yml | 13 +++- CMakeLists.txt | 5 ++ include/pybind11/cast.h | 14 +++- include/pybind11/detail/common.h | 4 + include/pybind11/numpy.h | 125 ++++++++++++++++++++++++++++--- tests/conftest.py | 1 + tests/pybind11_tests.cpp | 6 ++ tests/test_eigen_matrix.py | 4 +- tests/test_numpy_array.py | 10 ++- tests/test_numpy_dtypes.cpp | 31 +++++++- tests/test_numpy_dtypes.py | 14 +++- 11 files changed, 206 insertions(+), 21 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cf03eebfd..fd44c1faa 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -108,12 +108,14 @@ jobs: run: python -m pip install pytest-github-actions-annotate-failures # First build - C++11 mode and inplace - # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here. + # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON here + # (same for PYBIND11_NUMPY_1_ONLY, but requires a NumPy 1.x at runtime). - name: Configure C++11 ${{ matrix.args }} run: > cmake -S . -B . -DPYBIND11_WERROR=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=ON + -DPYBIND11_NUMPY_1_ONLY=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=11 @@ -138,11 +140,13 @@ jobs: # Second build - C++17 mode and in a build directory # More-or-less randomly adding -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF here. + # (same for PYBIND11_NUMPY_1_ONLY, but requires a NumPy 1.x at runtime). - name: Configure C++17 run: > cmake -S . -B build2 -DPYBIND11_WERROR=ON -DPYBIND11_SIMPLE_GIL_MANAGEMENT=OFF + -DPYBIND11_NUMPY_1_ONLY=ON -DDOWNLOAD_CATCH=ON -DDOWNLOAD_EIGEN=ON -DCMAKE_CXX_STANDARD=17 @@ -660,6 +664,11 @@ jobs: run: | python3 -m pip install cmake -r tests/requirements.txt + - name: Ensure NumPy 2 is used (required Python >= 3.9) + if: matrix.container == 'almalinux:9' + run: | + python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1' + - name: Configure shell: bash run: > @@ -895,8 +904,10 @@ jobs: python-version: ${{ matrix.python }} - name: Prepare env + # Ensure use of NumPy 2 (via NumPy nightlies but can be changed soon) run: | python3 -m pip install -r tests/requirements.txt + python3 -m pip install 'numpy>=2.0.0b1' 'scipy>=1.13.0rc1' - name: Update CMake uses: jwlawson/actions-setup-cmake@v2.0 diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e75e99eb..890440dc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -109,6 +109,8 @@ option(PYBIND11_TEST "Build pybind11 test suite?" ${PYBIND11_MASTER_PROJECT}) option(PYBIND11_NOPYTHON "Disable search for Python" OFF) option(PYBIND11_SIMPLE_GIL_MANAGEMENT "Use simpler GIL management logic that does not support disassociation" OFF) +option(PYBIND11_NUMPY_1_ONLY + "Disable NumPy 2 support to avoid changes to previous pybind11 versions." OFF) set(PYBIND11_INTERNALS_VERSION "" CACHE STRING "Override the ABI version, may be used to enable the unstable ABI.") @@ -116,6 +118,9 @@ set(PYBIND11_INTERNALS_VERSION if(PYBIND11_SIMPLE_GIL_MANAGEMENT) add_compile_definitions(PYBIND11_SIMPLE_GIL_MANAGEMENT) endif() +if(PYBIND11_NUMPY_1_ONLY) + add_compile_definitions(PYBIND11_NUMPY_1_ONLY) +endif() cmake_dependent_option( USE_PYTHON_INCLUDE_DIR diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 8b5beb0ef..3f3f966d0 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -327,8 +327,9 @@ public: value = false; return true; } - if (convert || (std::strcmp("numpy.bool_", Py_TYPE(src.ptr())->tp_name) == 0)) { - // (allow non-implicit conversion for numpy booleans) + if (convert || is_numpy_bool(src)) { + // (allow non-implicit conversion for numpy booleans), use strncmp + // since NumPy 1.x had an additional trailing underscore. Py_ssize_t res = -1; if (src.is_none()) { @@ -360,6 +361,15 @@ public: return handle(src ? Py_True : Py_False).inc_ref(); } PYBIND11_TYPE_CASTER(bool, const_name("bool")); + +private: + // Test if an object is a NumPy boolean (without fetching the type). + static inline bool is_numpy_bool(handle object) { + const char *type_name = Py_TYPE(object.ptr())->tp_name; + // Name changed to `numpy.bool` in NumPy 2, `numpy.bool_` is needed for 1.x support + return std::strcmp("numpy.bool", type_name) == 0 + || std::strcmp("numpy.bool_", type_name) == 0; + } }; // Helper class for UTF-{8,16,32} C++ stl strings: diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h index 83800e960..9f8e13e24 100644 --- a/include/pybind11/detail/common.h +++ b/include/pybind11/detail/common.h @@ -296,6 +296,10 @@ PYBIND11_WARNING_DISABLE_MSVC(4505) # undef copysign #endif +#if defined(PYBIND11_NUMPY_1_ONLY) +# define PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED +#endif + #if defined(PYPY_VERSION) && !defined(PYBIND11_SIMPLE_GIL_MANAGEMENT) # define PYBIND11_SIMPLE_GIL_MANAGEMENT #endif diff --git a/include/pybind11/numpy.h b/include/pybind11/numpy.h index 8551aa264..12a0490e2 100644 --- a/include/pybind11/numpy.h +++ b/include/pybind11/numpy.h @@ -29,10 +29,15 @@ #include #include +#if defined(PYBIND11_NUMPY_1_ONLY) && !defined(PYBIND11_INTERNAL_NUMPY_1_ONLY_DETECTED) +# error PYBIND11_NUMPY_1_ONLY must be defined before any pybind11 header is included. +#endif + /* This will be true on all flat address space platforms and allows us to reduce the whole npy_intp / ssize_t / Py_intptr_t business down to just ssize_t for all size and dimension types (e.g. shape, strides, indexing), instead of inflicting this - upon the library user. */ + upon the library user. + Note that NumPy 2 now uses ssize_t for `npy_intp` to simplify this. */ static_assert(sizeof(::pybind11::ssize_t) == sizeof(Py_intptr_t), "ssize_t != Py_intptr_t"); static_assert(std::is_signed::value, "Py_intptr_t must be signed"); // We now can reinterpret_cast between py::ssize_t and Py_intptr_t (MSVC + PyPy cares) @@ -53,7 +58,8 @@ struct handle_type_name { template struct npy_format_descriptor; -struct PyArrayDescr_Proxy { +/* NumPy 1 proxy (always includes legacy fields) */ +struct PyArrayDescr1_Proxy { PyObject_HEAD PyObject *typeobj; char kind; @@ -68,6 +74,43 @@ struct PyArrayDescr_Proxy { PyObject *names; }; +#ifndef PYBIND11_NUMPY_1_ONLY +struct PyArrayDescr_Proxy { + PyObject_HEAD + PyObject *typeobj; + char kind; + char type; + char byteorder; + char _former_flags; + int type_num; + /* Additional fields are NumPy version specific. */ +}; +#else +/* NumPy 1.x only, we can expose all fields */ +using PyArrayDescr_Proxy = PyArrayDescr1_Proxy; +#endif + +/* NumPy 2 proxy, including legacy fields */ +struct PyArrayDescr2_Proxy { + PyObject_HEAD + PyObject *typeobj; + char kind; + char type; + char byteorder; + char _former_flags; + int type_num; + std::uint64_t flags; + ssize_t elsize; + ssize_t alignment; + PyObject *metadata; + Py_hash_t hash; + void *reserved_null[2]; + /* The following fields only exist if 0 <= type_num < 2056 */ + char *subarray; + PyObject *fields; + PyObject *names; +}; + struct PyArray_Proxy { PyObject_HEAD char *data; @@ -131,6 +174,14 @@ PYBIND11_NOINLINE module_ import_numpy_core_submodule(const char *submodule_name object numpy_version = numpy_lib.attr("NumpyVersion")(version_string); int major_version = numpy_version.attr("major").cast(); +#ifdef PYBIND11_NUMPY_1_ONLY + if (major_version >= 2) { + throw std::runtime_error( + "This extension was built with PYBIND11_NUMPY_1_ONLY defined, " + "but NumPy 2 is used in this process. For NumPy2 compatibility, " + "this extension needs to be rebuilt without the PYBIND11_NUMPY_1_ONLY define."); + } +#endif /* `numpy.core` was renamed to `numpy._core` in NumPy 2.0 as it officially became a private module. */ std::string numpy_core_path = major_version >= 2 ? "numpy._core" : "numpy.core"; @@ -203,6 +254,8 @@ struct npy_api { NPY_ULONG_, NPY_ULONGLONG_, NPY_UINT_), }; + unsigned int PyArray_RUNTIME_VERSION_; + struct PyArray_Dims { Py_intptr_t *ptr; int len; @@ -241,6 +294,7 @@ struct npy_api { PyObject *(*PyArray_FromAny_)(PyObject *, PyObject *, int, int, int, PyObject *); int (*PyArray_DescrConverter_)(PyObject *, PyObject **); bool (*PyArray_EquivTypes_)(PyObject *, PyObject *); +#ifdef PYBIND11_NUMPY_1_ONLY int (*PyArray_GetArrayParamsFromObject_)(PyObject *, PyObject *, unsigned char, @@ -249,6 +303,7 @@ struct npy_api { Py_intptr_t *, PyObject **, PyObject *); +#endif PyObject *(*PyArray_Squeeze_)(PyObject *); // Unused. Not removed because that affects ABI of the class. int (*PyArray_SetBaseObject_)(PyObject *, PyObject *); @@ -266,7 +321,8 @@ private: API_PyArray_DescrFromScalar = 57, API_PyArray_FromAny = 69, API_PyArray_Resize = 80, - API_PyArray_CopyInto = 82, + // CopyInto was slot 82 and 50 was effectively an alias. NumPy 2 removed 82. + API_PyArray_CopyInto = 50, API_PyArray_NewCopy = 85, API_PyArray_NewFromDescr = 94, API_PyArray_DescrNewFromType = 96, @@ -275,7 +331,9 @@ private: API_PyArray_View = 137, API_PyArray_DescrConverter = 174, API_PyArray_EquivTypes = 182, +#ifdef PYBIND11_NUMPY_1_ONLY API_PyArray_GetArrayParamsFromObject = 278, +#endif API_PyArray_SetBaseObject = 282 }; @@ -290,7 +348,8 @@ private: npy_api api; #define DECL_NPY_API(Func) api.Func##_ = (decltype(api.Func##_)) api_ptr[API_##Func]; DECL_NPY_API(PyArray_GetNDArrayCFeatureVersion); - if (api.PyArray_GetNDArrayCFeatureVersion_() < 0x7) { + api.PyArray_RUNTIME_VERSION_ = api.PyArray_GetNDArrayCFeatureVersion_(); + if (api.PyArray_RUNTIME_VERSION_ < 0x7) { pybind11_fail("pybind11 numpy support requires numpy >= 1.7.0"); } DECL_NPY_API(PyArray_Type); @@ -309,7 +368,9 @@ private: DECL_NPY_API(PyArray_View); DECL_NPY_API(PyArray_DescrConverter); DECL_NPY_API(PyArray_EquivTypes); +#ifdef PYBIND11_NUMPY_1_ONLY DECL_NPY_API(PyArray_GetArrayParamsFromObject); +#endif DECL_NPY_API(PyArray_SetBaseObject); #undef DECL_NPY_API @@ -331,6 +392,14 @@ inline const PyArrayDescr_Proxy *array_descriptor_proxy(const PyObject *ptr) { return reinterpret_cast(ptr); } +inline const PyArrayDescr1_Proxy *array_descriptor1_proxy(const PyObject *ptr) { + return reinterpret_cast(ptr); +} + +inline const PyArrayDescr2_Proxy *array_descriptor2_proxy(const PyObject *ptr) { + return reinterpret_cast(ptr); +} + inline bool check_flags(const void *ptr, int flag) { return (flag == (array_proxy(ptr)->flags & flag)); } @@ -610,10 +679,32 @@ public: } /// Size of the data type in bytes. +#ifdef PYBIND11_NUMPY_1_ONLY ssize_t itemsize() const { return detail::array_descriptor_proxy(m_ptr)->elsize; } +#else + ssize_t itemsize() const { + if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { + return detail::array_descriptor1_proxy(m_ptr)->elsize; + } + return detail::array_descriptor2_proxy(m_ptr)->elsize; + } +#endif /// Returns true for structured data types. +#ifdef PYBIND11_NUMPY_1_ONLY bool has_fields() const { return detail::array_descriptor_proxy(m_ptr)->names != nullptr; } +#else + bool has_fields() const { + if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { + return detail::array_descriptor1_proxy(m_ptr)->names != nullptr; + } + const auto *proxy = detail::array_descriptor2_proxy(m_ptr); + if (proxy->type_num < 0 || proxy->type_num >= 2056) { + return false; + } + return proxy->names != nullptr; + } +#endif /// Single-character code for dtype's kind. /// For example, floating point types are 'f' and integral types are 'i'. @@ -639,11 +730,29 @@ public: /// Single character for byteorder char byteorder() const { return detail::array_descriptor_proxy(m_ptr)->byteorder; } - /// Alignment of the data type +/// Alignment of the data type +#ifdef PYBIND11_NUMPY_1_ONLY int alignment() const { return detail::array_descriptor_proxy(m_ptr)->alignment; } +#else + ssize_t alignment() const { + if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { + return detail::array_descriptor1_proxy(m_ptr)->alignment; + } + return detail::array_descriptor2_proxy(m_ptr)->alignment; + } +#endif - /// Flags for the array descriptor +/// Flags for the array descriptor +#ifdef PYBIND11_NUMPY_1_ONLY char flags() const { return detail::array_descriptor_proxy(m_ptr)->flags; } +#else + std::uint64_t flags() const { + if (detail::npy_api::get().PyArray_RUNTIME_VERSION_ < 0x12) { + return (unsigned char) detail::array_descriptor1_proxy(m_ptr)->flags; + } + return detail::array_descriptor2_proxy(m_ptr)->flags; + } +#endif private: static object &_dtype_from_pep3118() { @@ -810,9 +919,7 @@ public: } /// Byte size of a single element - ssize_t itemsize() const { - return detail::array_descriptor_proxy(detail::array_proxy(m_ptr)->descr)->elsize; - } + ssize_t itemsize() const { return dtype().itemsize(); } /// Total number of bytes ssize_t nbytes() const { return size() * itemsize(); } diff --git a/tests/conftest.py b/tests/conftest.py index ad5b47b4b..8ebc70222 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -218,4 +218,5 @@ def pytest_report_header(config): f" {pybind11_tests.cpp_std}" f" {pybind11_tests.PYBIND11_INTERNALS_ID}" f" PYBIND11_SIMPLE_GIL_MANAGEMENT={pybind11_tests.PYBIND11_SIMPLE_GIL_MANAGEMENT}" + f" PYBIND11_NUMPY_1_ONLY={pybind11_tests.PYBIND11_NUMPY_1_ONLY}" ) diff --git a/tests/pybind11_tests.cpp b/tests/pybind11_tests.cpp index 4578e4b7f..81869ebe2 100644 --- a/tests/pybind11_tests.cpp +++ b/tests/pybind11_tests.cpp @@ -95,6 +95,12 @@ PYBIND11_MODULE(pybind11_tests, m) { #else false; #endif + m.attr("PYBIND11_NUMPY_1_ONLY") = +#if defined(PYBIND11_NUMPY_1_ONLY) + true; +#else + false; +#endif bind_ConstructorStats(m); diff --git a/tests/test_eigen_matrix.py b/tests/test_eigen_matrix.py index a486c2f93..9a2cafc0a 100644 --- a/tests/test_eigen_matrix.py +++ b/tests/test_eigen_matrix.py @@ -608,7 +608,9 @@ def test_both_ref_mutators(): def test_nocopy_wrapper(): # get_elem requires a column-contiguous matrix reference, but should be # callable with other types of matrix (via copying): - int_matrix_colmajor = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]], order="F") + int_matrix_colmajor = np.array( + [[1, 2, 3], [4, 5, 6], [7, 8, 9]], dtype="l", order="F" + ) dbl_matrix_colmajor = np.array( int_matrix_colmajor, dtype="double", order="F", copy=True ) diff --git a/tests/test_numpy_array.py b/tests/test_numpy_array.py index 0697daf3e..25ad09ec3 100644 --- a/tests/test_numpy_array.py +++ b/tests/test_numpy_array.py @@ -536,7 +536,12 @@ def test_format_descriptors_for_floating_point_types(test_func): @pytest.mark.parametrize("contiguity", [None, "C", "F"]) @pytest.mark.parametrize("noconvert", [False, True]) @pytest.mark.filterwarnings( - "ignore:Casting complex values to real discards the imaginary part:numpy.ComplexWarning" + "ignore:Casting complex values to real discards the imaginary part:" + + ( + "numpy.exceptions.ComplexWarning" + if hasattr(np, "exceptions") + else "numpy.ComplexWarning" + ) ) def test_argument_conversions(forcecast, contiguity, noconvert): function_name = "accept_double" @@ -583,7 +588,8 @@ def test_argument_conversions(forcecast, contiguity, noconvert): def test_dtype_refcount_leak(): from sys import getrefcount - dtype = np.dtype(np.float_) + # Was np.float_ but that alias for float64 was removed in NumPy 2. + dtype = np.dtype(np.float64) a = np.array([1], dtype=dtype) before = getrefcount(dtype) m.ndim(a) diff --git a/tests/test_numpy_dtypes.cpp b/tests/test_numpy_dtypes.cpp index 053004a22..519b69f40 100644 --- a/tests/test_numpy_dtypes.cpp +++ b/tests/test_numpy_dtypes.cpp @@ -405,10 +405,35 @@ TEST_SUBMODULE(numpy_dtypes, m) { }); // test_dtype + // Below we use `L` for unsigned long as unfortunately the only name that + // works reliably on Both NumPy 2.x and old NumPy 1.x. std::vector dtype_names{ - "byte", "short", "intc", "int_", "longlong", "ubyte", "ushort", - "uintc", "uint", "ulonglong", "half", "single", "double", "longdouble", - "csingle", "cdouble", "clongdouble", "bool_", "datetime64", "timedelta64", "object_"}; + "byte", + "short", + "intc", + "long", + "longlong", + "ubyte", + "ushort", + "uintc", + "L", + "ulonglong", + "half", + "single", + "double", + "longdouble", + "csingle", + "cdouble", + "clongdouble", + "bool_", + "datetime64", + "timedelta64", + "object_", + // platform dependent aliases (int_ and uint are also NumPy version dependent on windows) + "int_", + "uint", + "intp", + "uintp"}; m.def("print_dtypes", []() { py::list l; diff --git a/tests/test_numpy_dtypes.py b/tests/test_numpy_dtypes.py index d10457eeb..e7854df4a 100644 --- a/tests/test_numpy_dtypes.py +++ b/tests/test_numpy_dtypes.py @@ -3,6 +3,7 @@ import re import pytest import env # noqa: F401 +from pybind11_tests import PYBIND11_NUMPY_1_ONLY from pybind11_tests import numpy_dtypes as m np = pytest.importorskip("numpy") @@ -172,13 +173,20 @@ def test_dtype(simple_dtype): np.zeros(1, m.trailing_padding_dtype()) ) - expected_chars = "bhilqBHILQefdgFDG?MmO" - assert m.test_dtype_kind() == list("iiiiiuuuuuffffcccbMmO") + expected_chars = list("bhilqBHILQefdgFDG?MmO") + # Note that int_ and uint size and mapping is NumPy version dependent: + expected_chars += [np.dtype(_).char for _ in ("int_", "uint", "intp", "uintp")] + assert m.test_dtype_kind() == list("iiiiiuuuuuffffcccbMmOiuiu") assert m.test_dtype_char_() == list(expected_chars) assert m.test_dtype_num() == [np.dtype(ch).num for ch in expected_chars] assert m.test_dtype_byteorder() == [np.dtype(ch).byteorder for ch in expected_chars] assert m.test_dtype_alignment() == [np.dtype(ch).alignment for ch in expected_chars] - assert m.test_dtype_flags() == [chr(np.dtype(ch).flags) for ch in expected_chars] + if not PYBIND11_NUMPY_1_ONLY: + assert m.test_dtype_flags() == [np.dtype(ch).flags for ch in expected_chars] + else: + assert m.test_dtype_flags() == [ + chr(np.dtype(ch).flags) for ch in expected_chars + ] def test_recarray(simple_dtype, packed_dtype):