-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handling warnings from C++ #4535
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a nice feature! Just a couple of comments.
if (result == 1) { | ||
return true; | ||
} | ||
if (result == -1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably have a more visible error / warning here right? Isn't -1 from IsSubclass supposed to be propagated to users?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is new commit OK? I am just not sure what "guidelines" on errors are applied for this specific scenario.
include/pybind11/pytypes.h
Outdated
PYBIND11_NAMESPACE_BEGIN(warnings) | ||
|
||
// Warning class | ||
static PyObject *warning_base = PyExc_Warning; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why aren't these const?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or even constexpr
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point. However, from what I understand constexpr won't work out of the box for non-const pointers. Changed to consts.
tests/test_warnings.py
Outdated
assert m.CustomWarning is not None | ||
assert issubclass(m.CustomWarning, DeprecationWarning) | ||
|
||
import warnings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why isn't this import at the top of the file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It was put there as local import for this specific test. Sure it can be moved around! Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there was a bit of a misunderstanding. You made those pointer to constants (which is incorrect, as shown by those incorrect const casts) instead of constant pointers.
See
include/pybind11/pybind11.h
Outdated
"PyExc_Warning!"); | ||
} | ||
|
||
if (PyErr_WarnEx(const_cast<PyObject *>(category), message, stack_level) == -1) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These const casts aren't safe
Just pinging for a review @Skylion007 and @henryiii - sorry for bothering and really long time to update this PR. |
/** | ||
* Wrapper to generate a new Python warning type. | ||
* | ||
* It is not (yet) possible to use PyObject as a py::base. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO the existing exception
type is a failed design that is better not replicated here. All it does is wrap a PyErr_NewException()
call, without any connection to the "reserved" template type
. It's more likely to be misleading/confusing than helpful. E.g. anyone uninitiated reading client code like this ...
... would think there is a formal connection between the C++ exception type and the Python exception type, but there is none here. That connection is (maybe) only established elsewhere, with an exception translator.
I think what we need here is something simpler and straightforward, for example:
PYBIND11_NAMESPACE_BEGIN(warnings)
inline object
new_warning_type(handle scope, const char *name, handle base = PyExc_RuntimeWarning) {
/* similar to what you have below */
}
// Similar to Python `warnings.warn()`
inline void
warn(const char *message, handle category = PyExc_RuntimeWarning, int stack_level = 2) {
/* similar to what you have below */
}
PYBIND11_NAMESPACE_END(warnings)
Could you please try that?
I think it's best implemented in a new pybind11/warnings.h file.
That's all I'd do. I definitely wouldn't want to introduce a bunch of aliases (warning_base
, bytes
, deprections
, ...), which are essentially just a level of obfuscation: people would have to learn ah, it's just PyExc_BytesWarning
etc.
I recommend closing this PR and starting a new one.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see the point. I have already opened new PR for it, let's continue there.
return true; | ||
} | ||
if (result == -1) { | ||
PyErr_Clear(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Never discard an error only to replace it with something more opaque: people will want to know what the "internal error" is (line below).
Best here: use raise_from()
followed by throw error_already_set()
.
[]() { py::raise_warning("RuntimeError should be raised!", PyExc_Exception); }); | ||
|
||
// Test custom warnings | ||
static py::warning<CustomWarning> my_warning(m, "CustomWarning", py::warnings::deprecation); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a very common pitfall:
The destructor may run (and call Py_DECREF
) only after the Python interpreter is finalized already.
Best to adopt this approach:
pybind11/tests/test_exceptions.cpp
Lines 119 to 121 in 43de801
PYBIND11_CONSTINIT static py::gil_safe_call_once_and_store<py::object> ex_storage; | |
ex_storage.call_once_and_store_result( | |
[&]() { return py::exception<MyException>(m, "MyException"); }); |
Description
Providing a simple interface to handle warnings on the pybind side. Following up #601 issue.
py::warning
class that provide wrapper for custom warnings.py::warnings
namespace containing shorthand versions of Python C API warning categories.raise_warning
that allow to use warnings directly frompy::warnings
namespace or created by the user.PS It is my first time contributing something "bigger" (finally! :)). Please, look at "TODO" section, there are some high-level decisions to be made. These are ones I have spotted. All reviews are welcome!
TODO:
stack_level
value is more relevant? C API is not defining defaultstack_level
. According to Python docs on warnings.warn the default value is1
. However, there is also a good point on the default to be set to2
:py::warnings
namespace a good name? Maybepy::warning_categories
is more descriptive and not conflicting withpy::warning
? ... or should it be removed entirely?Suggested changelog entry: