gh-125854: Improve error messages for invalid category in the warnings module (GH-137750)

Include the type name if the category is a type, but not a Warning
subclass, instead of just 'type'.
This commit is contained in:
Serhiy Storchaka 2025-08-14 14:59:04 +03:00 committed by GitHub
parent 2a6888ea14
commit c47ffbf1a3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 31 additions and 38 deletions

View file

@ -449,9 +449,12 @@ def warn(message, category=None, stacklevel=1, source=None,
# Check category argument # Check category argument
if category is None: if category is None:
category = UserWarning category = UserWarning
if not (isinstance(category, type) and issubclass(category, Warning)): elif not isinstance(category, type):
raise TypeError("category must be a Warning subclass, " raise TypeError(f"category must be a Warning subclass, not "
"not '{:s}'".format(type(category).__name__)) f"'{type(category).__name__}'")
elif not issubclass(category, Warning):
raise TypeError(f"category must be a Warning subclass, not "
f"class '{category.__name__}'")
if not isinstance(skip_file_prefixes, tuple): if not isinstance(skip_file_prefixes, tuple):
# The C version demands a tuple for implementation performance. # The C version demands a tuple for implementation performance.
raise TypeError('skip_file_prefixes must be a tuple of strs.') raise TypeError('skip_file_prefixes must be a tuple of strs.')

View file

@ -596,25 +596,19 @@ def test_warning_classes(self):
class MyWarningClass(Warning): class MyWarningClass(Warning):
pass pass
class NonWarningSubclass:
pass
# passing a non-subclass of Warning should raise a TypeError # passing a non-subclass of Warning should raise a TypeError
with self.assertRaises(TypeError) as cm: expected = "category must be a Warning subclass, not 'str'"
with self.assertRaisesRegex(TypeError, expected):
self.module.warn('bad warning category', '') self.module.warn('bad warning category', '')
self.assertIn('category must be a Warning subclass, not ',
str(cm.exception))
with self.assertRaises(TypeError) as cm: expected = "category must be a Warning subclass, not class 'int'"
self.module.warn('bad warning category', NonWarningSubclass) with self.assertRaisesRegex(TypeError, expected):
self.assertIn('category must be a Warning subclass, not ', self.module.warn('bad warning category', int)
str(cm.exception))
# check that warning instances also raise a TypeError # check that warning instances also raise a TypeError
with self.assertRaises(TypeError) as cm: expected = "category must be a Warning subclass, not '.*MyWarningClass'"
with self.assertRaisesRegex(TypeError, expected):
self.module.warn('bad warning category', MyWarningClass()) self.module.warn('bad warning category', MyWarningClass())
self.assertIn('category must be a Warning subclass, not ',
str(cm.exception))
with self.module.catch_warnings(): with self.module.catch_warnings():
self.module.resetwarnings() self.module.resetwarnings()

View file

@ -0,0 +1 @@
Improve error messages for invalid category in :func:`warnings.warn`.

View file

@ -823,11 +823,7 @@ warn_explicit(PyThreadState *tstate, PyObject *category, PyObject *message,
/* Normalize message. */ /* Normalize message. */
Py_INCREF(message); /* DECREF'ed in cleanup. */ Py_INCREF(message); /* DECREF'ed in cleanup. */
rc = PyObject_IsInstance(message, PyExc_Warning); if (PyObject_TypeCheck(message, (PyTypeObject *)PyExc_Warning)) {
if (rc == -1) {
goto cleanup;
}
if (rc == 1) {
text = PyObject_Str(message); text = PyObject_Str(message);
if (text == NULL) if (text == NULL)
goto cleanup; goto cleanup;
@ -1124,26 +1120,25 @@ setup_context(Py_ssize_t stack_level,
static PyObject * static PyObject *
get_category(PyObject *message, PyObject *category) get_category(PyObject *message, PyObject *category)
{ {
int rc; if (PyObject_TypeCheck(message, (PyTypeObject *)PyExc_Warning)) {
/* Ignore the category argument. */
/* Get category. */ return (PyObject*)Py_TYPE(message);
rc = PyObject_IsInstance(message, PyExc_Warning); }
if (rc == -1) if (category == NULL || category == Py_None) {
return NULL; return PyExc_UserWarning;
}
if (rc == 1)
category = (PyObject*)Py_TYPE(message);
else if (category == NULL || category == Py_None)
category = PyExc_UserWarning;
/* Validate category. */ /* Validate category. */
rc = PyObject_IsSubclass(category, PyExc_Warning); if (!PyType_Check(category)) {
/* category is not a subclass of PyExc_Warning or
PyObject_IsSubclass raised an error */
if (rc == -1 || rc == 0) {
PyErr_Format(PyExc_TypeError, PyErr_Format(PyExc_TypeError,
"category must be a Warning subclass, not '%s'", "category must be a Warning subclass, not '%T'",
Py_TYPE(category)->tp_name); category);
return NULL;
}
if (!PyType_IsSubtype((PyTypeObject *)category, (PyTypeObject *)PyExc_Warning)) {
PyErr_Format(PyExc_TypeError,
"category must be a Warning subclass, not class '%N'",
(PyTypeObject *)category);
return NULL; return NULL;
} }