Skip to content

Commit ad15208

Browse files
gh-125854: Improve error messages for invalid category in warnings.warn()
Include the type name if category is a type, but not a Warning subtype, instead of just 'type'.
1 parent 7a703c8 commit ad15208

File tree

4 files changed

+31
-38
lines changed

4 files changed

+31
-38
lines changed

Lib/_py_warnings.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -449,9 +449,12 @@ def warn(message, category=None, stacklevel=1, source=None,
449449
# Check category argument
450450
if category is None:
451451
category = UserWarning
452-
if not (isinstance(category, type) and issubclass(category, Warning)):
453-
raise TypeError("category must be a Warning subclass, "
454-
"not '{:s}'".format(type(category).__name__))
452+
elif not isinstance(category, type):
453+
raise TypeError(f"category must be a Warning subclass, not "
454+
f"'{type(category).__name__}'")
455+
elif not issubclass(category, Warning):
456+
raise TypeError(f"category must be a Warning subclass, not "
457+
f"class '{category.__name__}'")
455458
if not isinstance(skip_file_prefixes, tuple):
456459
# The C version demands a tuple for implementation performance.
457460
raise TypeError('skip_file_prefixes must be a tuple of strs.')

Lib/test/test_warnings/__init__.py

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -596,25 +596,19 @@ def test_warning_classes(self):
596596
class MyWarningClass(Warning):
597597
pass
598598

599-
class NonWarningSubclass:
600-
pass
601-
602599
# passing a non-subclass of Warning should raise a TypeError
603-
with self.assertRaises(TypeError) as cm:
600+
expected = "category must be a Warning subclass, not 'str'"
601+
with self.assertRaisesRegex(TypeError, expected):
604602
self.module.warn('bad warning category', '')
605-
self.assertIn('category must be a Warning subclass, not ',
606-
str(cm.exception))
607603

608-
with self.assertRaises(TypeError) as cm:
609-
self.module.warn('bad warning category', NonWarningSubclass)
610-
self.assertIn('category must be a Warning subclass, not ',
611-
str(cm.exception))
604+
expected = "category must be a Warning subclass, not class 'int'"
605+
with self.assertRaisesRegex(TypeError, expected):
606+
self.module.warn('bad warning category', int)
612607

613608
# check that warning instances also raise a TypeError
614-
with self.assertRaises(TypeError) as cm:
609+
expected = "category must be a Warning subclass, not '.*MyWarningClass'"
610+
with self.assertRaisesRegex(TypeError, expected):
615611
self.module.warn('bad warning category', MyWarningClass())
616-
self.assertIn('category must be a Warning subclass, not ',
617-
str(cm.exception))
618612

619613
with self.module.catch_warnings():
620614
self.module.resetwarnings()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Improve error messages for invalid category in :func:`warnings.warn`.

Python/_warnings.c

Lines changed: 17 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -823,11 +823,7 @@ warn_explicit(PyThreadState *tstate, PyObject *category, PyObject *message,
823823

824824
/* Normalize message. */
825825
Py_INCREF(message); /* DECREF'ed in cleanup. */
826-
rc = PyObject_IsInstance(message, PyExc_Warning);
827-
if (rc == -1) {
828-
goto cleanup;
829-
}
830-
if (rc == 1) {
826+
if (PyObject_TypeCheck(message, (PyTypeObject *)PyExc_Warning)) {
831827
text = PyObject_Str(message);
832828
if (text == NULL)
833829
goto cleanup;
@@ -1124,26 +1120,25 @@ setup_context(Py_ssize_t stack_level,
11241120
static PyObject *
11251121
get_category(PyObject *message, PyObject *category)
11261122
{
1127-
int rc;
1128-
1129-
/* Get category. */
1130-
rc = PyObject_IsInstance(message, PyExc_Warning);
1131-
if (rc == -1)
1132-
return NULL;
1133-
1134-
if (rc == 1)
1135-
category = (PyObject*)Py_TYPE(message);
1136-
else if (category == NULL || category == Py_None)
1137-
category = PyExc_UserWarning;
1123+
if (PyObject_TypeCheck(message, (PyTypeObject *)PyExc_Warning)) {
1124+
/* Ignore the category argument. */
1125+
return (PyObject*)Py_TYPE(message);
1126+
}
1127+
if (category == NULL || category == Py_None) {
1128+
return PyExc_UserWarning;
1129+
}
11381130

11391131
/* Validate category. */
1140-
rc = PyObject_IsSubclass(category, PyExc_Warning);
1141-
/* category is not a subclass of PyExc_Warning or
1142-
PyObject_IsSubclass raised an error */
1143-
if (rc == -1 || rc == 0) {
1132+
if (!PyType_Check(category)) {
1133+
PyErr_Format(PyExc_TypeError,
1134+
"category must be a Warning subclass, not '%T'",
1135+
category);
1136+
return NULL;
1137+
}
1138+
if (!PyType_IsSubtype((PyTypeObject *)category, (PyTypeObject *)PyExc_Warning)) {
11441139
PyErr_Format(PyExc_TypeError,
1145-
"category must be a Warning subclass, not '%s'",
1146-
Py_TYPE(category)->tp_name);
1140+
"category must be a Warning subclass, not class '%N'",
1141+
(PyTypeObject *)category);
11471142
return NULL;
11481143
}
11491144

0 commit comments

Comments
 (0)