Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 64ceef9

Browse files
committed
bpo-29672: Save and restore module warning registries in catch_warnings
This avoids catch_warnings wiping out all module warning registries due to calling _filters_mutated. Which causes all warnings recorded in those registries to be shown again.
1 parent 16dfca4 commit 64ceef9

File tree

3 files changed

+81
-4
lines changed

3 files changed

+81
-4
lines changed

Lib/test/test_warnings/__init__.py

+9
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,15 @@ def test_check_warnings(self):
10631063
with support.check_warnings(('foo', RuntimeWarning)):
10641064
wmod.warn("foo")
10651065

1066+
def test_check_warnings_restore_registries(self):
1067+
global __warningregistry__
1068+
wmod = self.module
1069+
orig_registry = __warningregistry__ = {}
1070+
with wmod.catch_warnings(module=wmod):
1071+
wmod.warn("foo")
1072+
assert len(__warningregistry__) != 0
1073+
assert len(__warningregistry__) == 0
1074+
10661075
class CCatchWarningTests(CatchWarningTests, unittest.TestCase):
10671076
module = c_warnings
10681077

Lib/warnings.py

+23-3
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ def _formatwarnmsg(msg):
116116
msg.filename, msg.lineno, line=msg.line)
117117
return _formatwarnmsg_impl(msg)
118118

119+
def _registrycleared(registry):
120+
"""Hook that notifies when a module warning registry is cleared."""
121+
119122
def filterwarnings(action, message="", category=Warning, module="", lineno=0,
120123
append=False):
121124
"""Insert an entry into the list of warnings filters (at the front).
@@ -328,6 +331,7 @@ def warn_explicit(message, category, filename, lineno,
328331
if registry is None:
329332
registry = {}
330333
if registry.get('version', 0) != _filters_version:
334+
_registrycleared(registry)
331335
registry.clear()
332336
registry['version'] = _filters_version
333337
if isinstance(message, Warning):
@@ -438,6 +442,7 @@ def __init__(self, *, record=False, module=None):
438442
self._record = record
439443
self._module = sys.modules['warnings'] if module is None else module
440444
self._entered = False
445+
self._old_registries = []
441446

442447
def __repr__(self):
443448
args = []
@@ -454,7 +459,9 @@ def __enter__(self):
454459
self._entered = True
455460
self._filters = self._module.filters
456461
self._module.filters = self._filters[:]
457-
self._module._filters_mutated()
462+
self._orig_registrycleared = self._module._registrycleared
463+
self._module._registrycleared = self._registrycleared
464+
self._filters_version = self._module._filters_mutated()
458465
self._showwarning = self._module.showwarning
459466
self._showwarnmsg_impl = self._module._showwarnmsg_impl
460467
if self._record:
@@ -471,10 +478,17 @@ def __exit__(self, *exc_info):
471478
if not self._entered:
472479
raise RuntimeError("Cannot exit %r without entering first" % self)
473480
self._module.filters = self._filters
474-
self._module._filters_mutated()
481+
self._module._registrycleared = self._orig_registrycleared
482+
self._module._set_filters_version(self._filters_version)
483+
for registry, registry_copy in self._old_registries:
484+
registry.clear()
485+
registry.update(registry_copy)
475486
self._module.showwarning = self._showwarning
476487
self._module._showwarnmsg_impl = self._showwarnmsg_impl
477488

489+
def _registrycleared(self, registry):
490+
self._old_registries.append((registry, registry.copy()))
491+
478492

479493
# Private utility function called by _PyErr_WarnUnawaitedCoroutine
480494
def _warn_unawaited_coroutine(coro):
@@ -509,7 +523,8 @@ def extract():
509523
# If either if the compiled regexs are None, match anything.
510524
try:
511525
from _warnings import (filters, _defaultaction, _onceregistry,
512-
warn, warn_explicit, _filters_mutated)
526+
warn, warn_explicit, _filters_mutated,
527+
_set_filters_version)
513528
defaultaction = _defaultaction
514529
onceregistry = _onceregistry
515530
_warnings_defaults = True
@@ -522,7 +537,12 @@ def extract():
522537

523538
def _filters_mutated():
524539
global _filters_version
540+
old_filters_version = _filters_version
525541
_filters_version += 1
542+
return old_filters_version
543+
544+
def _set_filters_version(filters_version):
545+
_filters_version = filters_version
526546

527547
_warnings_defaults = False
528548

Python/_warnings.c

+49-1
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,39 @@ get_filter(PyObject *category, PyObject *text, Py_ssize_t lineno,
243243
}
244244

245245

246+
static int
247+
call_registrycleared(PyObject *registry)
248+
{
249+
PyObject *_registrycleared, *res;
250+
_Py_IDENTIFIER(_registrycleared);
251+
252+
_registrycleared = get_warnings_attr(&PyId__registrycleared, 0);
253+
if (_registrycleared == NULL) {
254+
if (PyErr_Occurred())
255+
return -1;
256+
return 0;
257+
}
258+
259+
if (!PyCallable_Check(_registrycleared)) {
260+
PyErr_SetString(PyExc_TypeError,
261+
"warnings._registrycleared() must be set to a callable");
262+
goto error;
263+
}
264+
265+
res = PyObject_CallFunctionObjArgs(_registrycleared, registry, NULL);
266+
Py_DECREF(_registrycleared);
267+
268+
if (res == NULL);
269+
return -1;
270+
271+
Py_DECREF(res);
272+
return 0;
273+
274+
error:
275+
Py_XDECREF(_registrycleared);
276+
return -1;
277+
}
278+
246279
static int
247280
already_warned(PyObject *registry, PyObject *key, int should_set)
248281
{
@@ -256,6 +289,7 @@ already_warned(PyObject *registry, PyObject *key, int should_set)
256289
if (version_obj == NULL
257290
|| !PyLong_CheckExact(version_obj)
258291
|| PyLong_AsLong(version_obj) != _PyRuntime.warnings.filters_version) {
292+
call_registrycleared(registry);
259293
PyDict_Clear(registry);
260294
version_obj = PyLong_FromLong(_PyRuntime.warnings.filters_version);
261295
if (version_obj == NULL)
@@ -910,7 +944,19 @@ warnings_warn_explicit(PyObject *self, PyObject *args, PyObject *kwds)
910944
static PyObject *
911945
warnings_filters_mutated(PyObject *self, PyObject *args)
912946
{
913-
_PyRuntime.warnings.filters_version++;
947+
return PyLong_FromLong(_PyRuntime.warnings.filters_version++);
948+
}
949+
950+
static PyObject *
951+
warnings_set_filters_version(PyObject *self, PyObject *args)
952+
{
953+
long filters_version;
954+
955+
if (!PyArg_ParseTuple(args, "l:_set_filters_version", &filters_version))
956+
return NULL;
957+
958+
_PyRuntime.warnings.filters_version = filters_version;
959+
914960
Py_RETURN_NONE;
915961
}
916962

@@ -1154,6 +1200,8 @@ static PyMethodDef warnings_functions[] = {
11541200
METH_VARARGS | METH_KEYWORDS, warn_explicit_doc},
11551201
{"_filters_mutated", (PyCFunction)warnings_filters_mutated, METH_NOARGS,
11561202
NULL},
1203+
{"_set_filters_version", (PyCFunction)warnings_set_filters_version,
1204+
METH_VARARGS, NULL},
11571205
/* XXX(brett.cannon): add showwarning? */
11581206
/* XXX(brett.cannon): Reasonable to add formatwarning? */
11591207
{NULL, NULL} /* sentinel */

0 commit comments

Comments
 (0)