-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Warnings wrappers to use from C++ (#5291)
* Add warning wrappers that allow to call warnings from pybind level * Add missing include for warnings.h * Change messages on failed checks, extend testing * clang-tidy fix * Refactor tests for warnings * Move handle before check * Remove unnecessary parametrized
- Loading branch information
Showing
6 changed files
with
194 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* | ||
pybind11/warnings.h: Python warnings wrappers. | ||
Copyright (c) 2024 Jan Iwaszkiewicz <[email protected]> | ||
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, | ||
"pybind11::detail::PyWarning_Check(): PyObject_IsSubclass() call failed."); | ||
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("pybind11::warnings::new_warning_type(): cannot create custom warning, base " | ||
"must be a subclass of " | ||
"PyExc_Warning!"); | ||
} | ||
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>() + std::string(".") + name; | ||
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(); | ||
} | ||
auto obj = reinterpret_steal<object>(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( | ||
"pybind11::warnings::warn(): 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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
/* | ||
tests/test_warnings.cpp -- usage of warnings::warn() and warnings categories. | ||
Copyright (c) 2024 Jan Iwaszkiewicz <[email protected]> | ||
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/warnings.h> | ||
|
||
#include "pybind11_tests.h" | ||
|
||
#include <utility> | ||
|
||
TEST_SUBMODULE(warnings_, m) { | ||
|
||
// Test warning mechanism base | ||
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("warn_with_default_category", []() { py::warnings::warn("This is RuntimeWarning"); }); | ||
|
||
m.def("warn_with_different_category", | ||
[]() { py::warnings::warn("This is FutureWarning", PyExc_FutureWarning); }); | ||
|
||
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<py::object> ex_storage; | ||
ex_storage.call_once_and_store_result([&]() { | ||
return py::warnings::new_warning_type(m, "CustomWarning", PyExc_DeprecationWarning); | ||
}); | ||
|
||
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); }); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
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, "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( | ||
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_wrong_subclass_fail(): | ||
with pytest.raises(Exception) as excinfo: | ||
m.warn_with_invalid_category() | ||
|
||
assert issubclass(excinfo.type, RuntimeError) | ||
assert ( | ||
str(excinfo.value) | ||
== "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.' | ||
) | ||
|
||
|
||
def test_warning_register(): | ||
assert m.CustomWarning is not None | ||
|
||
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 str(excinfo[0].message) == "This is warning from Python!" | ||
|
||
|
||
def test_warning_custom(): | ||
with pytest.warns(m.CustomWarning) as excinfo: | ||
value = m.warn_with_custom_type() | ||
|
||
assert issubclass(excinfo[0].category, DeprecationWarning) | ||
assert str(excinfo[0].message) == "This is CustomWarning" | ||
assert value == 37 |