From 0afa32f932f016f3e657865dcaf8e8bc37fb06b9 Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Fri, 9 Aug 2024 01:24:33 +0200 Subject: [PATCH 1/7] Add warning wrappers that allow to call warnings from pybind level --- CMakeLists.txt | 3 +- include/pybind11/warnings.h | 70 ++++++++++++++++++++++++ tests/CMakeLists.txt | 3 +- tests/test_warnings.cpp | 76 ++++++++++++++++++++++++++ tests/test_warnings.py | 104 ++++++++++++++++++++++++++++++++++++ 5 files changed, 254 insertions(+), 2 deletions(-) create mode 100644 include/pybind11/warnings.h create mode 100644 tests/test_warnings.cpp create mode 100644 tests/test_warnings.py diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e5b8c8f31..3955caad9e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,7 +180,8 @@ set(PYBIND11_HEADERS include/pybind11/stl_bind.h include/pybind11/stl/filesystem.h include/pybind11/type_caster_pyobject_ptr.h - include/pybind11/typing.h) + include/pybind11/typing.h + include/pybind11/warnings.h) # Compare with grep and warn if mismatched if(PYBIND11_MASTER_PROJECT AND NOT CMAKE_VERSION VERSION_LESS 3.12) diff --git a/include/pybind11/warnings.h b/include/pybind11/warnings.h new file mode 100644 index 0000000000..81e1ce7bbe --- /dev/null +++ b/include/pybind11/warnings.h @@ -0,0 +1,70 @@ +/* + pybind11/warnings.h: Python warnings wrappers. + + Copyright (c) 2024 Jan Iwaszkiewicz + + 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 "pybind11.h" +#include "detail/common.h" + +PYBIND11_NAMESPACE_BEGIN(PYBIND11_NAMESPACE) + +PYBIND11_NAMESPACE_BEGIN(detail) + +inline bool PyWarning_Check(PyObject *obj) { + int result = PyObject_IsSubclass(obj, PyExc_Warning); + if (result == 1) { + return true; + } + if (result == -1) { + raise_from(PyExc_SystemError, + "PyWarning_Check(): internal error of Python C API while " + "checking a subclass of the object!"); + throw error_already_set(); + } + return false; +} + +PYBIND11_NAMESPACE_END(detail) + +PYBIND11_NAMESPACE_BEGIN(warnings) + +inline object +new_warning_type(handle scope, const char *name, handle base = PyExc_RuntimeWarning) { + if (!detail::PyWarning_Check(base.ptr())) { + pybind11_fail("warning(): cannot create custom warning, base must be a subclass of " + "PyExc_Warning!"); + } + if (hasattr(scope, "__dict__") && scope.attr("__dict__").contains(name)) { + pybind11_fail("Error during initialization: multiple incompatible " + "definitions with name \"" + + std::string(name) + "\""); + } + std::string full_name = scope.attr("__name__").cast() + std::string(".") + name; + handle h(PyErr_NewException(const_cast(full_name.c_str()), base.ptr(), nullptr)); + object obj = reinterpret_steal(h); + scope.attr(name) = obj; + return obj; +} + +// Similar to Python `warnings.warn()` +inline void +warn(const char *message, handle category = PyExc_RuntimeWarning, int stack_level = 2) { + if (!detail::PyWarning_Check(category.ptr())) { + pybind11_fail("raise_warning(): cannot raise warning, category must be a subclass of " + "PyExc_Warning!"); + } + + if (PyErr_WarnEx(category.ptr(), message, stack_level) == -1) { + throw error_already_set(); + } +} + +PYBIND11_NAMESPACE_END(warnings) + +PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index aae9be720b..3bcb278e5e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -162,7 +162,8 @@ set(PYBIND11_TEST_FILES test_unnamed_namespace_a test_unnamed_namespace_b test_vector_unique_ptr_member - test_virtual_functions) + test_virtual_functions + test_warnings) # Invoking cmake with something like: # cmake -DPYBIND11_TEST_OVERRIDE="test_callbacks.cpp;test_pickling.cpp" .. diff --git a/tests/test_warnings.cpp b/tests/test_warnings.cpp new file mode 100644 index 0000000000..383e88835b --- /dev/null +++ b/tests/test_warnings.cpp @@ -0,0 +1,76 @@ +/* + tests/test_warnings.cpp -- usage of warnings::warn() and warnings categories. + + Copyright (c) 2024 Jan Iwaszkiewicz + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include + +#include "pybind11_tests.h" + +#include + +namespace warning_helpers { +void warn_function(py::module &m, const char *name, py::handle category, const char *message) { + m.def(name, [category, message]() { py::warnings::warn(message, category); }); +} +} // namespace warning_helpers + +class CustomWarning {}; + +TEST_SUBMODULE(warnings_, m) { + + // Test warning mechanism base + m.def("raise_and_return", []() { + std::string message = "Warning was raised!"; + py::warnings::warn(message.c_str(), PyExc_Warning); + return 21; + }); + + m.def("raise_default", []() { py::warnings::warn("RuntimeWarning is raised!"); }); + + m.def("raise_from_cpython", + []() { py::warnings::warn("UnicodeWarning is raised!", PyExc_UnicodeWarning); }); + + m.def("raise_and_fail", + []() { py::warnings::warn("RuntimeError should be raised!", PyExc_Exception); }); + + // Test custom warnings + PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store ex_storage; + ex_storage.call_once_and_store_result([&]() { + return py::warnings::new_warning_type(m, "CustomWarning", PyExc_DeprecationWarning); + }); + + m.def("raise_custom", []() { + py::warnings::warn("CustomWarning was raised!", ex_storage.get_stored()); + return 37; + }); + + // Bind warning categories + warning_helpers::warn_function(m, "raise_base_warning", PyExc_Warning, "This is Warning!"); + warning_helpers::warn_function( + m, "raise_bytes_warning", PyExc_BytesWarning, "This is BytesWarning!"); + warning_helpers::warn_function( + m, "raise_deprecation_warning", PyExc_DeprecationWarning, "This is DeprecationWarning!"); + warning_helpers::warn_function( + m, "raise_future_warning", PyExc_FutureWarning, "This is FutureWarning!"); + warning_helpers::warn_function( + m, "raise_import_warning", PyExc_ImportWarning, "This is ImportWarning!"); + warning_helpers::warn_function(m, + "raise_pending_deprecation_warning", + PyExc_PendingDeprecationWarning, + "This is PendingDeprecationWarning!"); + warning_helpers::warn_function( + m, "raise_resource_warning", PyExc_ResourceWarning, "This is ResourceWarning!"); + warning_helpers::warn_function( + m, "raise_runtime_warning", PyExc_RuntimeWarning, "This is RuntimeWarning!"); + warning_helpers::warn_function( + m, "raise_syntax_warning", PyExc_SyntaxWarning, "This is SyntaxWarning!"); + warning_helpers::warn_function( + m, "raise_unicode_warning", PyExc_UnicodeWarning, "This is UnicodeWarning!"); + warning_helpers::warn_function( + m, "raise_user_warning", PyExc_UserWarning, "This is UserWarning!"); +} diff --git a/tests/test_warnings.py b/tests/test_warnings.py new file mode 100644 index 0000000000..ac3ae399b9 --- /dev/null +++ b/tests/test_warnings.py @@ -0,0 +1,104 @@ +from __future__ import annotations + +import warnings + +import pytest + +import pybind11_tests # noqa: F401 +from pybind11_tests import warnings_ as m + + +@pytest.mark.parametrize( + ("expected_category", "expected_message", "expected_value", "module_function"), + [ + (Warning, "Warning was raised!", 21, m.raise_and_return), + (RuntimeWarning, "RuntimeWarning is raised!", None, m.raise_default), + (UnicodeWarning, "UnicodeWarning is raised!", None, m.raise_from_cpython), + ], +) +def test_warning_simple( + expected_category, expected_message, expected_value, module_function +): + with pytest.warns(Warning) as excinfo: + value = module_function() + + assert issubclass(excinfo[0].category, expected_category) + assert str(excinfo[0].message) == expected_message + assert value == expected_value + + +def test_warning_fail(): + with pytest.raises(Exception) as excinfo: + m.raise_and_fail() + + assert issubclass(excinfo.type, RuntimeError) + assert ( + str(excinfo.value) + == "raise_warning(): cannot raise warning, category must be a subclass of PyExc_Warning!" + ) + + +def test_warning_register(): + assert m.CustomWarning is not None + assert issubclass(m.CustomWarning, DeprecationWarning) + + with pytest.warns(m.CustomWarning) as excinfo: + warnings.warn("This is warning from Python!", m.CustomWarning, stacklevel=1) + + assert issubclass(excinfo[0].category, DeprecationWarning) + assert issubclass(excinfo[0].category, m.CustomWarning) + assert str(excinfo[0].message) == "This is warning from Python!" + + +@pytest.mark.parametrize( + ( + "expected_category", + "expected_base", + "expected_message", + "expected_value", + "module_function", + ), + [ + ( + m.CustomWarning, + DeprecationWarning, + "CustomWarning was raised!", + 37, + m.raise_custom, + ), + ], +) +def test_warning_custom( + expected_category, expected_base, expected_message, expected_value, module_function +): + with pytest.warns(expected_category) as excinfo: + value = module_function() + + assert issubclass(excinfo[0].category, expected_base) + assert issubclass(excinfo[0].category, expected_category) + assert str(excinfo[0].message) == expected_message + assert value == expected_value + + +@pytest.mark.parametrize( + ("expected_category", "module_function"), + [ + (Warning, m.raise_base_warning), + (BytesWarning, m.raise_bytes_warning), + (DeprecationWarning, m.raise_deprecation_warning), + (FutureWarning, m.raise_future_warning), + (ImportWarning, m.raise_import_warning), + (PendingDeprecationWarning, m.raise_pending_deprecation_warning), + (ResourceWarning, m.raise_resource_warning), + (RuntimeWarning, m.raise_runtime_warning), + (SyntaxWarning, m.raise_syntax_warning), + (UnicodeWarning, m.raise_unicode_warning), + (UserWarning, m.raise_user_warning), + ], +) +def test_warning_categories(expected_category, module_function): + with pytest.warns(Warning) as excinfo: + module_function() + + assert issubclass(excinfo[0].category, expected_category) + assert str(excinfo[0].message) == f"This is {expected_category.__name__}!" From 3233262ff6a8eb8384ec3725612d8a8790e90e2a Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Thu, 15 Aug 2024 02:18:27 +0200 Subject: [PATCH 2/7] Add missing include for warnings.h --- tests/extra_python_package/test_files.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/extra_python_package/test_files.py b/tests/extra_python_package/test_files.py index aedbdf1c1e..b52b2ac0d8 100644 --- a/tests/extra_python_package/test_files.py +++ b/tests/extra_python_package/test_files.py @@ -48,6 +48,7 @@ "include/pybind11/stl_bind.h", "include/pybind11/type_caster_pyobject_ptr.h", "include/pybind11/typing.h", + "include/pybind11/warnings.h", } detail_headers = { From 0dcd9177b60d3f2e07a2c05b62823da7bd6bd22a Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Thu, 15 Aug 2024 03:25:04 +0200 Subject: [PATCH 3/7] Change messages on failed checks, extend testing --- include/pybind11/warnings.h | 28 +++++++++++++++++----------- tests/test_warnings.cpp | 3 +++ tests/test_warnings.py | 15 +++++++++++++-- 3 files changed, 33 insertions(+), 13 deletions(-) diff --git a/include/pybind11/warnings.h b/include/pybind11/warnings.h index 81e1ce7bbe..76d3b37463 100644 --- a/include/pybind11/warnings.h +++ b/include/pybind11/warnings.h @@ -23,8 +23,7 @@ inline bool PyWarning_Check(PyObject *obj) { } if (result == -1) { raise_from(PyExc_SystemError, - "PyWarning_Check(): internal error of Python C API while " - "checking a subclass of the object!"); + "pybind11::detail::PyWarning_Check(): PyObject_IsSubclass() call failed."); throw error_already_set(); } return false; @@ -37,17 +36,23 @@ PYBIND11_NAMESPACE_BEGIN(warnings) inline object new_warning_type(handle scope, const char *name, handle base = PyExc_RuntimeWarning) { if (!detail::PyWarning_Check(base.ptr())) { - pybind11_fail("warning(): cannot create custom warning, base must be a subclass of " + pybind11_fail("pybind11::warnings::new_warning_type(): cannot create custom warning, base " + "must be a subclass of " "PyExc_Warning!"); } - if (hasattr(scope, "__dict__") && scope.attr("__dict__").contains(name)) { - pybind11_fail("Error during initialization: multiple incompatible " - "definitions with name \"" - + std::string(name) + "\""); + if (hasattr(scope, name)) { + pybind11_fail("pybind11::warnings::new_warning_type(): an attribute with name \"" + + std::string(name) + "\" exists already."); } std::string full_name = scope.attr("__name__").cast() + std::string(".") + name; - handle h(PyErr_NewException(const_cast(full_name.c_str()), base.ptr(), nullptr)); - object obj = reinterpret_steal(h); + auto new_ex = PyErr_NewException(const_cast(full_name.c_str()), base.ptr(), nullptr); + if (!new_ex) { + raise_from(PyExc_SystemError, + "pybind11::warnings::new_warning_type(): PyErr_NewException() call failed."); + throw error_already_set(); + } + handle h(new_ex); + auto obj = reinterpret_steal(h); scope.attr(name) = obj; return obj; } @@ -56,8 +61,9 @@ new_warning_type(handle scope, const char *name, handle base = PyExc_RuntimeWarn inline void warn(const char *message, handle category = PyExc_RuntimeWarning, int stack_level = 2) { if (!detail::PyWarning_Check(category.ptr())) { - pybind11_fail("raise_warning(): cannot raise warning, category must be a subclass of " - "PyExc_Warning!"); + pybind11_fail( + "pybind11::warnings::warn(): cannot raise warning, category must be a subclass of " + "PyExc_Warning!"); } if (PyErr_WarnEx(category.ptr(), message, stack_level) == -1) { diff --git a/tests/test_warnings.cpp b/tests/test_warnings.cpp index 383e88835b..6105d8add6 100644 --- a/tests/test_warnings.cpp +++ b/tests/test_warnings.cpp @@ -49,6 +49,9 @@ TEST_SUBMODULE(warnings_, m) { return 37; }); + m.def("register_duplicate_warning", + [m]() { py::warnings::new_warning_type(m, "CustomWarning", PyExc_RuntimeWarning); }); + // Bind warning categories warning_helpers::warn_function(m, "raise_base_warning", PyExc_Warning, "This is Warning!"); warning_helpers::warn_function( diff --git a/tests/test_warnings.py b/tests/test_warnings.py index ac3ae399b9..420244c5dc 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -27,14 +27,25 @@ def test_warning_simple( assert value == expected_value -def test_warning_fail(): +def test_warning_wrong_subclass_fail(): with pytest.raises(Exception) as excinfo: m.raise_and_fail() assert issubclass(excinfo.type, RuntimeError) assert ( str(excinfo.value) - == "raise_warning(): cannot raise warning, category must be a subclass of PyExc_Warning!" + == "pybind11::warnings::warn(): cannot raise warning, category must be a subclass of PyExc_Warning!" + ) + + +def test_warning_double_register_fail(): + with pytest.raises(Exception) as excinfo: + m.register_duplicate_warning() + + assert issubclass(excinfo.type, RuntimeError) + assert ( + str(excinfo.value) + == 'pybind11::warnings::new_warning_type(): an attribute with name "CustomWarning" exists already.' ) From 1f5005062d53ba646992d79ea166eb8fa5e31524 Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Thu, 15 Aug 2024 03:47:52 +0200 Subject: [PATCH 4/7] clang-tidy fix --- include/pybind11/warnings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/pybind11/warnings.h b/include/pybind11/warnings.h index 76d3b37463..3b0037a510 100644 --- a/include/pybind11/warnings.h +++ b/include/pybind11/warnings.h @@ -45,7 +45,7 @@ new_warning_type(handle scope, const char *name, handle base = PyExc_RuntimeWarn + std::string(name) + "\" exists already."); } std::string full_name = scope.attr("__name__").cast() + std::string(".") + name; - auto new_ex = PyErr_NewException(const_cast(full_name.c_str()), base.ptr(), nullptr); + auto *new_ex = PyErr_NewException(const_cast(full_name.c_str()), base.ptr(), nullptr); if (!new_ex) { raise_from(PyExc_SystemError, "pybind11::warnings::new_warning_type(): PyErr_NewException() call failed."); From 76d9e10dd877f85aadaaa54118e32158f5e4a8ca Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Mon, 19 Aug 2024 03:08:56 +0200 Subject: [PATCH 5/7] Refactor tests for warnings --- tests/test_warnings.cpp | 51 ++++++++--------------------------------- tests/test_warnings.py | 36 +++++------------------------ 2 files changed, 15 insertions(+), 72 deletions(-) diff --git a/tests/test_warnings.cpp b/tests/test_warnings.cpp index 6105d8add6..e76f21249f 100644 --- a/tests/test_warnings.cpp +++ b/tests/test_warnings.cpp @@ -13,30 +13,22 @@ #include -namespace warning_helpers { -void warn_function(py::module &m, const char *name, py::handle category, const char *message) { - m.def(name, [category, message]() { py::warnings::warn(message, category); }); -} -} // namespace warning_helpers - -class CustomWarning {}; - TEST_SUBMODULE(warnings_, m) { // Test warning mechanism base - m.def("raise_and_return", []() { - std::string message = "Warning was raised!"; + m.def("warn_and_return_value", []() { + std::string message = "This is simple warning"; py::warnings::warn(message.c_str(), PyExc_Warning); return 21; }); - m.def("raise_default", []() { py::warnings::warn("RuntimeWarning is raised!"); }); + m.def("warn_with_default_category", []() { py::warnings::warn("This is RuntimeWarning"); }); - m.def("raise_from_cpython", - []() { py::warnings::warn("UnicodeWarning is raised!", PyExc_UnicodeWarning); }); + m.def("warn_with_different_category", + []() { py::warnings::warn("This is FutureWarning", PyExc_FutureWarning); }); - m.def("raise_and_fail", - []() { py::warnings::warn("RuntimeError should be raised!", PyExc_Exception); }); + m.def("warn_with_invalid_category", + []() { py::warnings::warn("Invalid category", PyExc_Exception); }); // Test custom warnings PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store ex_storage; @@ -44,36 +36,11 @@ TEST_SUBMODULE(warnings_, m) { return py::warnings::new_warning_type(m, "CustomWarning", PyExc_DeprecationWarning); }); - m.def("raise_custom", []() { - py::warnings::warn("CustomWarning was raised!", ex_storage.get_stored()); + m.def("warn_with_custom_type", []() { + py::warnings::warn("This is CustomWarning", ex_storage.get_stored()); return 37; }); m.def("register_duplicate_warning", [m]() { py::warnings::new_warning_type(m, "CustomWarning", PyExc_RuntimeWarning); }); - - // Bind warning categories - warning_helpers::warn_function(m, "raise_base_warning", PyExc_Warning, "This is Warning!"); - warning_helpers::warn_function( - m, "raise_bytes_warning", PyExc_BytesWarning, "This is BytesWarning!"); - warning_helpers::warn_function( - m, "raise_deprecation_warning", PyExc_DeprecationWarning, "This is DeprecationWarning!"); - warning_helpers::warn_function( - m, "raise_future_warning", PyExc_FutureWarning, "This is FutureWarning!"); - warning_helpers::warn_function( - m, "raise_import_warning", PyExc_ImportWarning, "This is ImportWarning!"); - warning_helpers::warn_function(m, - "raise_pending_deprecation_warning", - PyExc_PendingDeprecationWarning, - "This is PendingDeprecationWarning!"); - warning_helpers::warn_function( - m, "raise_resource_warning", PyExc_ResourceWarning, "This is ResourceWarning!"); - warning_helpers::warn_function( - m, "raise_runtime_warning", PyExc_RuntimeWarning, "This is RuntimeWarning!"); - warning_helpers::warn_function( - m, "raise_syntax_warning", PyExc_SyntaxWarning, "This is SyntaxWarning!"); - warning_helpers::warn_function( - m, "raise_unicode_warning", PyExc_UnicodeWarning, "This is UnicodeWarning!"); - warning_helpers::warn_function( - m, "raise_user_warning", PyExc_UserWarning, "This is UserWarning!"); } diff --git a/tests/test_warnings.py b/tests/test_warnings.py index 420244c5dc..3338ad5233 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -11,9 +11,9 @@ @pytest.mark.parametrize( ("expected_category", "expected_message", "expected_value", "module_function"), [ - (Warning, "Warning was raised!", 21, m.raise_and_return), - (RuntimeWarning, "RuntimeWarning is raised!", None, m.raise_default), - (UnicodeWarning, "UnicodeWarning is raised!", None, m.raise_from_cpython), + (Warning, "This is simple warning", 21, m.warn_and_return_value), + (RuntimeWarning, "This is RuntimeWarning", None, m.warn_with_default_category), + (FutureWarning, "This is FutureWarning", None, m.warn_with_different_category), ], ) def test_warning_simple( @@ -29,7 +29,7 @@ def test_warning_simple( def test_warning_wrong_subclass_fail(): with pytest.raises(Exception) as excinfo: - m.raise_and_fail() + m.warn_with_invalid_category() assert issubclass(excinfo.type, RuntimeError) assert ( @@ -73,9 +73,9 @@ def test_warning_register(): ( m.CustomWarning, DeprecationWarning, - "CustomWarning was raised!", + "This is CustomWarning", 37, - m.raise_custom, + m.warn_with_custom_type, ), ], ) @@ -89,27 +89,3 @@ def test_warning_custom( assert issubclass(excinfo[0].category, expected_category) assert str(excinfo[0].message) == expected_message assert value == expected_value - - -@pytest.mark.parametrize( - ("expected_category", "module_function"), - [ - (Warning, m.raise_base_warning), - (BytesWarning, m.raise_bytes_warning), - (DeprecationWarning, m.raise_deprecation_warning), - (FutureWarning, m.raise_future_warning), - (ImportWarning, m.raise_import_warning), - (PendingDeprecationWarning, m.raise_pending_deprecation_warning), - (ResourceWarning, m.raise_resource_warning), - (RuntimeWarning, m.raise_runtime_warning), - (SyntaxWarning, m.raise_syntax_warning), - (UnicodeWarning, m.raise_unicode_warning), - (UserWarning, m.raise_user_warning), - ], -) -def test_warning_categories(expected_category, module_function): - with pytest.warns(Warning) as excinfo: - module_function() - - assert issubclass(excinfo[0].category, expected_category) - assert str(excinfo[0].message) == f"This is {expected_category.__name__}!" From f4cf539eefa0ae6cded1543883c0d7c0eb5b04b1 Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Mon, 19 Aug 2024 03:24:25 +0200 Subject: [PATCH 6/7] Move handle before check --- include/pybind11/warnings.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/include/pybind11/warnings.h b/include/pybind11/warnings.h index 3b0037a510..263b2990e3 100644 --- a/include/pybind11/warnings.h +++ b/include/pybind11/warnings.h @@ -45,13 +45,12 @@ new_warning_type(handle scope, const char *name, handle base = PyExc_RuntimeWarn + std::string(name) + "\" exists already."); } std::string full_name = scope.attr("__name__").cast() + std::string(".") + name; - auto *new_ex = PyErr_NewException(const_cast(full_name.c_str()), base.ptr(), nullptr); - if (!new_ex) { + handle h(PyErr_NewException(full_name.c_str(), base.ptr(), nullptr)); + if (!h) { raise_from(PyExc_SystemError, "pybind11::warnings::new_warning_type(): PyErr_NewException() call failed."); throw error_already_set(); } - handle h(new_ex); auto obj = reinterpret_steal(h); scope.attr(name) = obj; return obj; From f98b5d4c897bb9bca52855033a4a6a119021b2a3 Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Mon, 26 Aug 2024 00:34:14 +0200 Subject: [PATCH 7/7] Remove unnecessary parametrized --- tests/test_warnings.py | 35 ++++++----------------------------- 1 file changed, 6 insertions(+), 29 deletions(-) diff --git a/tests/test_warnings.py b/tests/test_warnings.py index 3338ad5233..4313432c33 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -51,41 +51,18 @@ def test_warning_double_register_fail(): def test_warning_register(): assert m.CustomWarning is not None - assert issubclass(m.CustomWarning, DeprecationWarning) with pytest.warns(m.CustomWarning) as excinfo: warnings.warn("This is warning from Python!", m.CustomWarning, stacklevel=1) assert issubclass(excinfo[0].category, DeprecationWarning) - assert issubclass(excinfo[0].category, m.CustomWarning) assert str(excinfo[0].message) == "This is warning from Python!" -@pytest.mark.parametrize( - ( - "expected_category", - "expected_base", - "expected_message", - "expected_value", - "module_function", - ), - [ - ( - m.CustomWarning, - DeprecationWarning, - "This is CustomWarning", - 37, - m.warn_with_custom_type, - ), - ], -) -def test_warning_custom( - expected_category, expected_base, expected_message, expected_value, module_function -): - with pytest.warns(expected_category) as excinfo: - value = module_function() +def test_warning_custom(): + with pytest.warns(m.CustomWarning) as excinfo: + value = m.warn_with_custom_type() - assert issubclass(excinfo[0].category, expected_base) - assert issubclass(excinfo[0].category, expected_category) - assert str(excinfo[0].message) == expected_message - assert value == expected_value + assert issubclass(excinfo[0].category, DeprecationWarning) + assert str(excinfo[0].message) == "This is CustomWarning" + assert value == 37