From 3f54c50d3ab8313b988f90f1c45ed25066c673ce Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Mon, 27 Feb 2023 00:02:36 +0100 Subject: [PATCH 1/8] Warnings wrappers, adding warning class and raise_warning() --- include/pybind11/pybind11.h | 31 +++++++++++++ include/pybind11/pytypes.h | 65 +++++++++++++++++++++++++++ tests/CMakeLists.txt | 3 +- tests/test_warnings.cpp | 70 +++++++++++++++++++++++++++++ tests/test_warnings.py | 87 +++++++++++++++++++++++++++++++++++++ 5 files changed, 255 insertions(+), 1 deletion(-) create mode 100644 tests/test_warnings.cpp create mode 100644 tests/test_warnings.py diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 6205effd61..e78c7c42d0 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -2544,6 +2544,37 @@ class exception : public object { void operator()(const char *message) { PyErr_SetString(m_ptr, message); } }; +/** + * Wrapper to generate a new Python warning type. + * + * It is not (yet) possible to use PyObject as a py::base. + * Template type argument is reserved for future use. + */ +template +class warning : public object { +public: + warning() = default; + warning(handle scope, const char *name, handle base = warnings::runtime) { + 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; + m_ptr = PyErr_NewException(const_cast(full_name.c_str()), base.ptr(), nullptr); + scope.attr(name) = *this; + } + + void operator()(const char *message, ssize_t stack_level = 2) { + raise_warning(message, *this, stack_level); + } +}; + PYBIND11_NAMESPACE_BEGIN(detail) // Returns a reference to a function-local static exception object used in the simple // register_exception approach below. (It would be simpler to have the static local variable diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index f11ed5da78..08b26bb29c 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -752,6 +752,71 @@ inline void raise_from(error_already_set &err, PyObject *type, const char *messa raise_from(type, message); } +PYBIND11_NAMESPACE_BEGIN(detail) +// Check if obj is a subclass of PyExc_Warning. +inline bool PyWarning_Check(PyObject *obj) { + int result = PyObject_IsSubclass(obj, PyExc_Warning); + if (result == 1) { + return true; + } + if (result == -1) { + PyErr_Clear(); + } + return false; +} +PYBIND11_NAMESPACE_END(detail) + +/// Namespace for Python warning categories +PYBIND11_NAMESPACE_BEGIN(warnings) + +// Warning class +static PyObject *warning_base = PyExc_Warning; + +// BytesWarning class +static PyObject *bytes = PyExc_BytesWarning; + +// DeprecationWarning class +static PyObject *deprecation = PyExc_DeprecationWarning; + +// FutureWarning class +static PyObject *future = PyExc_FutureWarning; + +// ImportWarning class +static PyObject *import = PyExc_ImportWarning; + +// PendingDeprecationWarning class +static PyObject *pending_deprecation = PyExc_PendingDeprecationWarning; + +// ResourceWarning class +static PyObject *resource = PyExc_ResourceWarning; + +// RuntimeWarning class +static PyObject *runtime = PyExc_RuntimeWarning; + +// RuntimeWarning class +static PyObject *syntax = PyExc_SyntaxWarning; + +// DeprecationWarning class +static PyObject *unicode = PyExc_UnicodeWarning; + +// UserWarning class +static PyObject *user = PyExc_UserWarning; + +PYBIND11_NAMESPACE_END(warnings) + +// Raise Python warning based on the Python warning category. +inline void +raise_warning(const char *message, handle category = warnings::runtime, ssize_t stack_level = 2) { + if (!pybind11::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(); + } +} + /** \defgroup python_builtins const_name Unless stated otherwise, the following C++ functions behave the same as their Python counterparts. diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index b1cb222b4a..0240ec1fe8 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -155,7 +155,8 @@ set(PYBIND11_TEST_FILES test_tagbased_polymorphic test_thread test_union - 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..1c5530b2ba --- /dev/null +++ b/tests/test_warnings.cpp @@ -0,0 +1,70 @@ +/* + tests/test_warnings.cpp -- usage of raise_warning() and warnings categories + + Copyright (c) 2023 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 "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::raise_warning(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::raise_warning(message.c_str(), py::warnings::warning_base); + return 21; + }); + + m.def("raise_default", []() { + py::raise_warning("RuntimeWarning is raised!"); + }); + + m.def("raise_from_cpython", []() { + py::raise_warning("UnicodeWarning is raised!", PyExc_UnicodeWarning); + }); + + m.def("raise_and_fail", []() { + py::raise_warning("RuntimeError should be raised!", PyExc_Exception); + }); + + // Test custom warnings + static py::warning my_warning(m, "CustomWarning", py::warnings::deprecation); + + m.def("raise_custom", []() { + py::raise_warning("CustomWarning was raised!", my_warning); + return 37; + }); + + m.def("raise_with_wrapper", []() { + my_warning("This is raised from a wrapper."); + return 42; + }); + + // Bind warning categories + warning_helpers::warn_function(m, "raise_base_warning", py::warnings::warning_base, "This is Warning!"); + warning_helpers::warn_function(m, "raise_bytes_warning", py::warnings::bytes, "This is BytesWarning!"); + warning_helpers::warn_function(m, "raise_deprecation_warning", py::warnings::deprecation, "This is DeprecationWarning!"); + warning_helpers::warn_function(m, "raise_future_warning", py::warnings::future, "This is FutureWarning!"); + warning_helpers::warn_function(m, "raise_import_warning", py::warnings::import, "This is ImportWarning!"); + warning_helpers::warn_function(m, "raise_pending_deprecation_warning", py::warnings::pending_deprecation, "This is PendingDeprecationWarning!"); + warning_helpers::warn_function(m, "raise_resource_warning", py::warnings::resource, "This is ResourceWarning!"); + warning_helpers::warn_function(m, "raise_runtime_warning", py::warnings::runtime, "This is RuntimeWarning!"); + warning_helpers::warn_function(m, "raise_syntax_warning", py::warnings::syntax, "This is SyntaxWarning!"); + warning_helpers::warn_function(m, "raise_unicode_warning", py::warnings::unicode, "This is UnicodeWarning!"); + warning_helpers::warn_function(m, "raise_user_warning", py::warnings::user, "This is UserWarning!"); +} diff --git a/tests/test_warnings.py b/tests/test_warnings.py new file mode 100644 index 0000000000..79e6dcdea5 --- /dev/null +++ b/tests/test_warnings.py @@ -0,0 +1,87 @@ +import sys + +import pytest + +import env +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) + + import warnings + + with pytest.warns(m.CustomWarning) as excinfo: + warnings.warn("This is warning from Python!", m.CustomWarning) + + 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), + (m.CustomWarning, DeprecationWarning, "This is raised from a wrapper.", 42, m.raise_with_wrapper), + ] +) +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) == "This is {}!".format(expected_category.__name__) From ed16550bb485337519448c2a8cb2d74d1ae12b2c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 26 Feb 2023 23:39:45 +0000 Subject: [PATCH 2/8] style: pre-commit fixes --- tests/test_warnings.cpp | 57 +++++++++++++++++++++++------------------ tests/test_warnings.py | 45 ++++++++++++++++++++++++-------- 2 files changed, 66 insertions(+), 36 deletions(-) diff --git a/tests/test_warnings.cpp b/tests/test_warnings.cpp index 1c5530b2ba..48f3f11073 100644 --- a/tests/test_warnings.cpp +++ b/tests/test_warnings.cpp @@ -12,11 +12,9 @@ #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::raise_warning(message, category); - }); - } +void warn_function(py::module &m, const char *name, py::handle category, const char *message) { + m.def(name, [category, message]() { py::raise_warning(message, category); }); +} } // namespace warning_helpers class CustomWarning {}; @@ -30,17 +28,13 @@ TEST_SUBMODULE(warnings_, m) { return 21; }); - m.def("raise_default", []() { - py::raise_warning("RuntimeWarning is raised!"); - }); + m.def("raise_default", []() { py::raise_warning("RuntimeWarning is raised!"); }); - m.def("raise_from_cpython", []() { - py::raise_warning("UnicodeWarning is raised!", PyExc_UnicodeWarning); - }); + m.def("raise_from_cpython", + []() { py::raise_warning("UnicodeWarning is raised!", PyExc_UnicodeWarning); }); - m.def("raise_and_fail", []() { - py::raise_warning("RuntimeError should be raised!", PyExc_Exception); - }); + m.def("raise_and_fail", + []() { py::raise_warning("RuntimeError should be raised!", PyExc_Exception); }); // Test custom warnings static py::warning my_warning(m, "CustomWarning", py::warnings::deprecation); @@ -56,15 +50,28 @@ TEST_SUBMODULE(warnings_, m) { }); // Bind warning categories - warning_helpers::warn_function(m, "raise_base_warning", py::warnings::warning_base, "This is Warning!"); - warning_helpers::warn_function(m, "raise_bytes_warning", py::warnings::bytes, "This is BytesWarning!"); - warning_helpers::warn_function(m, "raise_deprecation_warning", py::warnings::deprecation, "This is DeprecationWarning!"); - warning_helpers::warn_function(m, "raise_future_warning", py::warnings::future, "This is FutureWarning!"); - warning_helpers::warn_function(m, "raise_import_warning", py::warnings::import, "This is ImportWarning!"); - warning_helpers::warn_function(m, "raise_pending_deprecation_warning", py::warnings::pending_deprecation, "This is PendingDeprecationWarning!"); - warning_helpers::warn_function(m, "raise_resource_warning", py::warnings::resource, "This is ResourceWarning!"); - warning_helpers::warn_function(m, "raise_runtime_warning", py::warnings::runtime, "This is RuntimeWarning!"); - warning_helpers::warn_function(m, "raise_syntax_warning", py::warnings::syntax, "This is SyntaxWarning!"); - warning_helpers::warn_function(m, "raise_unicode_warning", py::warnings::unicode, "This is UnicodeWarning!"); - warning_helpers::warn_function(m, "raise_user_warning", py::warnings::user, "This is UserWarning!"); + warning_helpers::warn_function( + m, "raise_base_warning", py::warnings::warning_base, "This is Warning!"); + warning_helpers::warn_function( + m, "raise_bytes_warning", py::warnings::bytes, "This is BytesWarning!"); + warning_helpers::warn_function( + m, "raise_deprecation_warning", py::warnings::deprecation, "This is DeprecationWarning!"); + warning_helpers::warn_function( + m, "raise_future_warning", py::warnings::future, "This is FutureWarning!"); + warning_helpers::warn_function( + m, "raise_import_warning", py::warnings::import, "This is ImportWarning!"); + warning_helpers::warn_function(m, + "raise_pending_deprecation_warning", + py::warnings::pending_deprecation, + "This is PendingDeprecationWarning!"); + warning_helpers::warn_function( + m, "raise_resource_warning", py::warnings::resource, "This is ResourceWarning!"); + warning_helpers::warn_function( + m, "raise_runtime_warning", py::warnings::runtime, "This is RuntimeWarning!"); + warning_helpers::warn_function( + m, "raise_syntax_warning", py::warnings::syntax, "This is SyntaxWarning!"); + warning_helpers::warn_function( + m, "raise_unicode_warning", py::warnings::unicode, "This is UnicodeWarning!"); + warning_helpers::warn_function( + m, "raise_user_warning", py::warnings::user, "This is UserWarning!"); } diff --git a/tests/test_warnings.py b/tests/test_warnings.py index 79e6dcdea5..d2c106d3ce 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -1,8 +1,6 @@ -import sys import pytest -import env import pybind11_tests # noqa: F401 from pybind11_tests import warnings_ as m @@ -13,9 +11,11 @@ (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): +def test_warning_simple( + expected_category, expected_message, expected_value, module_function +): with pytest.warns(Warning) as excinfo: value = module_function() @@ -29,7 +29,10 @@ def test_warning_fail(): 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!" + assert ( + str(excinfo.value) + == "raise_warning(): cannot raise warning, category must be a subclass of PyExc_Warning!" + ) def test_warning_register(): @@ -47,13 +50,33 @@ def test_warning_register(): @pytest.mark.parametrize( - ("expected_category", "expected_base", "expected_message", "expected_value", "module_function"), + ( + "expected_category", + "expected_base", + "expected_message", + "expected_value", + "module_function", + ), [ - (m.CustomWarning, DeprecationWarning, "CustomWarning was raised!", 37, m.raise_custom), - (m.CustomWarning, DeprecationWarning, "This is raised from a wrapper.", 42, m.raise_with_wrapper), - ] + ( + m.CustomWarning, + DeprecationWarning, + "CustomWarning was raised!", + 37, + m.raise_custom, + ), + ( + m.CustomWarning, + DeprecationWarning, + "This is raised from a wrapper.", + 42, + m.raise_with_wrapper, + ), + ], ) -def test_warning_custom(expected_category, expected_base, expected_message, expected_value, module_function): +def test_warning_custom( + expected_category, expected_base, expected_message, expected_value, module_function +): with pytest.warns(expected_category) as excinfo: value = module_function() @@ -84,4 +107,4 @@ def test_warning_categories(expected_category, module_function): module_function() assert issubclass(excinfo[0].category, expected_category) - assert str(excinfo[0].message) == "This is {}!".format(expected_category.__name__) + assert str(excinfo[0].message) == f"This is {expected_category.__name__}!" From 78235ac2639e7f97e8e71df712581cc71d8fb8b4 Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Fri, 17 Mar 2023 00:44:42 +0100 Subject: [PATCH 3/8] Adjust to const, apply comments --- include/pybind11/pybind11.h | 26 ++++++++++++++++++++--- include/pybind11/pytypes.h | 41 ++++++++++++++----------------------- tests/test_warnings.cpp | 2 +- tests/test_warnings.py | 3 +-- 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index e78c7c42d0..7f7f7d5190 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -2554,8 +2554,8 @@ template class warning : public object { public: warning() = default; - warning(handle scope, const char *name, handle base = warnings::runtime) { - if (!detail::PyWarning_Check(base.ptr())) { + warning(handle scope, const char *name, const PyObject *base = PyExc_RuntimeWarning) { + if (!detail::PyWarning_Check(base)) { pybind11_fail("warning(): cannot create custom warning, base must be a subclass of " "PyExc_Warning!"); } @@ -2566,7 +2566,9 @@ class warning : public object { } std::string full_name = scope.attr("__name__").cast() + std::string(".") + name; - m_ptr = PyErr_NewException(const_cast(full_name.c_str()), base.ptr(), nullptr); + m_ptr = PyErr_NewException( + const_cast(full_name.c_str()), const_cast(base), nullptr + ); scope.attr(name) = *this; } @@ -2575,6 +2577,24 @@ class warning : public object { } }; +// Raise Python warning based on the Python warning category. +inline void +raise_warning(const char *message, const PyObject *category = PyExc_RuntimeWarning, ssize_t stack_level = 2) { + if (!pybind11::detail::PyWarning_Check(category)) { + pybind11_fail("raise_warning(): cannot raise warning, category must be a subclass of " + "PyExc_Warning!"); + } + + if (PyErr_WarnEx(const_cast(category), message, stack_level) == -1) { + throw error_already_set(); + } +} + +template +void raise_warning(const char *message, warning category, ssize_t stack_level = 2) { + raise_warning(message, category.ptr(), stack_level); +} + PYBIND11_NAMESPACE_BEGIN(detail) // Returns a reference to a function-local static exception object used in the simple // register_exception approach below. (It would be simpler to have the static local variable diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 08b26bb29c..41c6862fc2 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -754,13 +754,15 @@ inline void raise_from(error_already_set &err, PyObject *type, const char *messa PYBIND11_NAMESPACE_BEGIN(detail) // Check if obj is a subclass of PyExc_Warning. -inline bool PyWarning_Check(PyObject *obj) { - int result = PyObject_IsSubclass(obj, PyExc_Warning); +inline bool PyWarning_Check(const PyObject *obj) { + int result = PyObject_IsSubclass(const_cast(obj), PyExc_Warning); if (result == 1) { return true; } if (result == -1) { PyErr_Clear(); + pybind11_fail("PyWarning_Check(): internal error of Python C API while " + "checking a subclass of the object!"); } return false; } @@ -770,53 +772,40 @@ PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_BEGIN(warnings) // Warning class -static PyObject *warning_base = PyExc_Warning; +static const PyObject *warning_base = PyExc_Warning; // BytesWarning class -static PyObject *bytes = PyExc_BytesWarning; +static const PyObject *bytes = PyExc_BytesWarning; // DeprecationWarning class -static PyObject *deprecation = PyExc_DeprecationWarning; +static const PyObject *deprecation = PyExc_DeprecationWarning; // FutureWarning class -static PyObject *future = PyExc_FutureWarning; +static const PyObject *future = PyExc_FutureWarning; // ImportWarning class -static PyObject *import = PyExc_ImportWarning; +static const PyObject *import = PyExc_ImportWarning; // PendingDeprecationWarning class -static PyObject *pending_deprecation = PyExc_PendingDeprecationWarning; +static const PyObject *pending_deprecation = PyExc_PendingDeprecationWarning; // ResourceWarning class -static PyObject *resource = PyExc_ResourceWarning; +static const PyObject *resource = PyExc_ResourceWarning; // RuntimeWarning class -static PyObject *runtime = PyExc_RuntimeWarning; +static const PyObject *runtime = PyExc_RuntimeWarning; // RuntimeWarning class -static PyObject *syntax = PyExc_SyntaxWarning; +static const PyObject *syntax = PyExc_SyntaxWarning; // DeprecationWarning class -static PyObject *unicode = PyExc_UnicodeWarning; +static const PyObject *unicode = PyExc_UnicodeWarning; // UserWarning class -static PyObject *user = PyExc_UserWarning; +static const PyObject *user = PyExc_UserWarning; PYBIND11_NAMESPACE_END(warnings) -// Raise Python warning based on the Python warning category. -inline void -raise_warning(const char *message, handle category = warnings::runtime, ssize_t stack_level = 2) { - if (!pybind11::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(); - } -} - /** \defgroup python_builtins const_name Unless stated otherwise, the following C++ functions behave the same as their Python counterparts. diff --git a/tests/test_warnings.cpp b/tests/test_warnings.cpp index 48f3f11073..30924393d3 100644 --- a/tests/test_warnings.cpp +++ b/tests/test_warnings.cpp @@ -12,7 +12,7 @@ #include namespace warning_helpers { -void warn_function(py::module &m, const char *name, py::handle category, const char *message) { +void warn_function(py::module &m, const char *name, const PyObject *category, const char *message) { m.def(name, [category, message]() { py::raise_warning(message, category); }); } } // namespace warning_helpers diff --git a/tests/test_warnings.py b/tests/test_warnings.py index d2c106d3ce..ca9f0d8d14 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -1,5 +1,6 @@ import pytest +import warnings import pybind11_tests # noqa: F401 from pybind11_tests import warnings_ as m @@ -39,8 +40,6 @@ def test_warning_register(): assert m.CustomWarning is not None assert issubclass(m.CustomWarning, DeprecationWarning) - import warnings - with pytest.warns(m.CustomWarning) as excinfo: warnings.warn("This is warning from Python!", m.CustomWarning) From ad9eaf109152462a72b38e48079e93fb934b69ba Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 16 Mar 2023 23:45:12 +0000 Subject: [PATCH 4/8] style: pre-commit fixes --- include/pybind11/pybind11.h | 8 ++++---- tests/test_warnings.cpp | 5 ++++- tests/test_warnings.py | 2 +- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 7f7f7d5190..378c16fc2e 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -2567,8 +2567,7 @@ class warning : public object { std::string full_name = scope.attr("__name__").cast() + std::string(".") + name; m_ptr = PyErr_NewException( - const_cast(full_name.c_str()), const_cast(base), nullptr - ); + const_cast(full_name.c_str()), const_cast(base), nullptr); scope.attr(name) = *this; } @@ -2578,8 +2577,9 @@ class warning : public object { }; // Raise Python warning based on the Python warning category. -inline void -raise_warning(const char *message, const PyObject *category = PyExc_RuntimeWarning, ssize_t stack_level = 2) { +inline void raise_warning(const char *message, + const PyObject *category = PyExc_RuntimeWarning, + ssize_t stack_level = 2) { if (!pybind11::detail::PyWarning_Check(category)) { pybind11_fail("raise_warning(): cannot raise warning, category must be a subclass of " "PyExc_Warning!"); diff --git a/tests/test_warnings.cpp b/tests/test_warnings.cpp index 30924393d3..d1a14967e6 100644 --- a/tests/test_warnings.cpp +++ b/tests/test_warnings.cpp @@ -12,7 +12,10 @@ #include namespace warning_helpers { -void warn_function(py::module &m, const char *name, const PyObject *category, const char *message) { +void warn_function(py::module &m, + const char *name, + const PyObject *category, + const char *message) { m.def(name, [category, message]() { py::raise_warning(message, category); }); } } // namespace warning_helpers diff --git a/tests/test_warnings.py b/tests/test_warnings.py index ca9f0d8d14..66121d40fe 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -1,6 +1,6 @@ +import warnings import pytest -import warnings import pybind11_tests # noqa: F401 from pybind11_tests import warnings_ as m From 0cc1c3e9a171854b42186f5b4986fe009fc46db2 Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Fri, 17 Mar 2023 01:16:56 +0100 Subject: [PATCH 5/8] Fix for ssize_t/int conversion --- include/pybind11/pybind11.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 378c16fc2e..8386bfc2e7 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -2571,7 +2571,7 @@ class warning : public object { scope.attr(name) = *this; } - void operator()(const char *message, ssize_t stack_level = 2) { + void operator()(const char *message, int stack_level = 2) { raise_warning(message, *this, stack_level); } }; @@ -2579,7 +2579,7 @@ class warning : public object { // Raise Python warning based on the Python warning category. inline void raise_warning(const char *message, const PyObject *category = PyExc_RuntimeWarning, - ssize_t stack_level = 2) { + int stack_level = 2) { if (!pybind11::detail::PyWarning_Check(category)) { pybind11_fail("raise_warning(): cannot raise warning, category must be a subclass of " "PyExc_Warning!"); @@ -2591,7 +2591,7 @@ inline void raise_warning(const char *message, } template -void raise_warning(const char *message, warning category, ssize_t stack_level = 2) { +void raise_warning(const char *message, warning category, int stack_level = 2) { raise_warning(message, category.ptr(), stack_level); } From 3dc83a4d4f290c17c21c0b5f1f917e38d50f40eb Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Mon, 20 Mar 2023 01:05:07 +0100 Subject: [PATCH 6/8] Change to constant pointers --- include/pybind11/pybind11.h | 21 +++++++-------------- include/pybind11/pytypes.h | 26 +++++++++++++------------- tests/test_warnings.cpp | 5 +---- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 8386bfc2e7..2515f967fe 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -2554,8 +2554,8 @@ template class warning : public object { public: warning() = default; - warning(handle scope, const char *name, const PyObject *base = PyExc_RuntimeWarning) { - if (!detail::PyWarning_Check(base)) { + warning(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!"); } @@ -2566,8 +2566,7 @@ class warning : public object { } std::string full_name = scope.attr("__name__").cast() + std::string(".") + name; - m_ptr = PyErr_NewException( - const_cast(full_name.c_str()), const_cast(base), nullptr); + m_ptr = PyErr_NewException(const_cast(full_name.c_str()), base.ptr(), nullptr); scope.attr(name) = *this; } @@ -2577,24 +2576,18 @@ class warning : public object { }; // Raise Python warning based on the Python warning category. -inline void raise_warning(const char *message, - const PyObject *category = PyExc_RuntimeWarning, - int stack_level = 2) { - if (!pybind11::detail::PyWarning_Check(category)) { +inline void +raise_warning(const char *message, handle category = PyExc_RuntimeWarning, int stack_level = 2) { + if (!pybind11::detail::PyWarning_Check(category.ptr())) { pybind11_fail("raise_warning(): cannot raise warning, category must be a subclass of " "PyExc_Warning!"); } - if (PyErr_WarnEx(const_cast(category), message, stack_level) == -1) { + if (PyErr_WarnEx(category.ptr(), message, stack_level) == -1) { throw error_already_set(); } } -template -void raise_warning(const char *message, warning category, int stack_level = 2) { - raise_warning(message, category.ptr(), stack_level); -} - PYBIND11_NAMESPACE_BEGIN(detail) // Returns a reference to a function-local static exception object used in the simple // register_exception approach below. (It would be simpler to have the static local variable diff --git a/include/pybind11/pytypes.h b/include/pybind11/pytypes.h index 41c6862fc2..0160069a45 100644 --- a/include/pybind11/pytypes.h +++ b/include/pybind11/pytypes.h @@ -754,8 +754,8 @@ inline void raise_from(error_already_set &err, PyObject *type, const char *messa PYBIND11_NAMESPACE_BEGIN(detail) // Check if obj is a subclass of PyExc_Warning. -inline bool PyWarning_Check(const PyObject *obj) { - int result = PyObject_IsSubclass(const_cast(obj), PyExc_Warning); +inline bool PyWarning_Check(PyObject *obj) { + int result = PyObject_IsSubclass(obj, PyExc_Warning); if (result == 1) { return true; } @@ -772,37 +772,37 @@ PYBIND11_NAMESPACE_END(detail) PYBIND11_NAMESPACE_BEGIN(warnings) // Warning class -static const PyObject *warning_base = PyExc_Warning; +static PyObject *const warning_base = PyExc_Warning; // BytesWarning class -static const PyObject *bytes = PyExc_BytesWarning; +static PyObject *const bytes = PyExc_BytesWarning; // DeprecationWarning class -static const PyObject *deprecation = PyExc_DeprecationWarning; +static PyObject *const deprecation = PyExc_DeprecationWarning; // FutureWarning class -static const PyObject *future = PyExc_FutureWarning; +static PyObject *const future = PyExc_FutureWarning; // ImportWarning class -static const PyObject *import = PyExc_ImportWarning; +static PyObject *const import = PyExc_ImportWarning; // PendingDeprecationWarning class -static const PyObject *pending_deprecation = PyExc_PendingDeprecationWarning; +static PyObject *const pending_deprecation = PyExc_PendingDeprecationWarning; // ResourceWarning class -static const PyObject *resource = PyExc_ResourceWarning; +static PyObject *const resource = PyExc_ResourceWarning; // RuntimeWarning class -static const PyObject *runtime = PyExc_RuntimeWarning; +static PyObject *const runtime = PyExc_RuntimeWarning; // RuntimeWarning class -static const PyObject *syntax = PyExc_SyntaxWarning; +static PyObject *const syntax = PyExc_SyntaxWarning; // DeprecationWarning class -static const PyObject *unicode = PyExc_UnicodeWarning; +static PyObject *const unicode = PyExc_UnicodeWarning; // UserWarning class -static const PyObject *user = PyExc_UserWarning; +static PyObject *const user = PyExc_UserWarning; PYBIND11_NAMESPACE_END(warnings) diff --git a/tests/test_warnings.cpp b/tests/test_warnings.cpp index d1a14967e6..48f3f11073 100644 --- a/tests/test_warnings.cpp +++ b/tests/test_warnings.cpp @@ -12,10 +12,7 @@ #include namespace warning_helpers { -void warn_function(py::module &m, - const char *name, - const PyObject *category, - const char *message) { +void warn_function(py::module &m, const char *name, py::handle category, const char *message) { m.def(name, [category, message]() { py::raise_warning(message, category); }); } } // namespace warning_helpers From 0dfebe169897a6138458de66999b2bd219833b3d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 14 Jul 2024 23:23:36 +0000 Subject: [PATCH 7/8] style: pre-commit fixes --- tests/test_warnings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_warnings.py b/tests/test_warnings.py index 66121d40fe..b1747f8e9b 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import warnings import pytest From d754b471733a6f7c3133669fe127439bcc2d31e8 Mon Sep 17 00:00:00 2001 From: jiwaszki Date: Mon, 15 Jul 2024 01:32:45 +0200 Subject: [PATCH 8/8] Remove unused imports and add stacklevel to warnings --- tests/test_warnings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_warnings.py b/tests/test_warnings.py index b1747f8e9b..bbcf2ff6a2 100644 --- a/tests/test_warnings.py +++ b/tests/test_warnings.py @@ -43,7 +43,7 @@ def test_warning_register(): assert issubclass(m.CustomWarning, DeprecationWarning) with pytest.warns(m.CustomWarning) as excinfo: - warnings.warn("This is warning from Python!", m.CustomWarning) + warnings.warn("This is warning from Python!", m.CustomWarning, stacklevel=1) assert issubclass(excinfo[0].category, DeprecationWarning) assert issubclass(excinfo[0].category, m.CustomWarning)