Skip to content

Commit a25815d

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

File tree

4 files changed

+43
-40
lines changed

4 files changed

+43
-40
lines changed

Lib/_py_warnings.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,15 @@ def _formatwarnmsg(msg):
251251
return _wm._formatwarnmsg_impl(msg)
252252

253253

254+
def _validate_category(category):
255+
if not isinstance(category, type):
256+
raise TypeError(f"category must be a Warning subclass, not "
257+
f"'{type(category).__name__}'")
258+
if not issubclass(category, Warning):
259+
raise TypeError(f"category must be a Warning subclass, not "
260+
f"class '{category.__name__}'")
261+
262+
254263
def filterwarnings(action, message="", category=Warning, module="", lineno=0,
255264
append=False):
256265
"""Insert an entry into the list of warnings filters (at the front).
@@ -267,8 +276,7 @@ def filterwarnings(action, message="", category=Warning, module="", lineno=0,
267276
raise ValueError(f"invalid action: {action!r}")
268277
if not isinstance(message, str):
269278
raise TypeError("message must be a string")
270-
if not isinstance(category, type) or not issubclass(category, Warning):
271-
raise TypeError("category must be a Warning subclass")
279+
_validate_category(category)
272280
if not isinstance(module, str):
273281
raise TypeError("module must be a string")
274282
if not isinstance(lineno, int):
@@ -449,9 +457,8 @@ def warn(message, category=None, stacklevel=1, source=None,
449457
# Check category argument
450458
if category is None:
451459
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__))
460+
else:
461+
_validate_category(category)
455462
if not isinstance(skip_file_prefixes, tuple):
456463
# The C version demands a tuple for implementation performance.
457464
raise TypeError('skip_file_prefixes must be a tuple of strs.')

Lib/test/test_warnings/__init__.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -596,25 +596,25 @@ 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))
603+
with self.assertRaisesRegex(TypeError, expected):
604+
self.module.filterwarnings('always', '', '')
607605

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))
606+
expected = "category must be a Warning subclass, not class 'int'"
607+
with self.assertRaisesRegex(TypeError, expected):
608+
self.module.warn('bad warning category', int)
609+
with self.assertRaisesRegex(TypeError, expected):
610+
self.module.filterwarnings('always', '', int)
612611

613612
# check that warning instances also raise a TypeError
614-
with self.assertRaises(TypeError) as cm:
613+
expected = "category must be a Warning subclass, not '.*MyWarningClass'"
614+
with self.assertRaisesRegex(TypeError, expected):
615615
self.module.warn('bad warning category', MyWarningClass())
616-
self.assertIn('category must be a Warning subclass, not ',
617-
str(cm.exception))
616+
with self.assertRaisesRegex(TypeError, expected):
617+
self.module.filterwarnings('always', '', MyWarningClass())
618618

619619
with self.module.catch_warnings():
620620
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 the :mod:`warnings` module.

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)