ignored.
*module*, if supplied, should be the module name.
- If no module is passed, the filename with ``.py`` stripped is used.
+ If no module is passed, the module regular expression in
+ :ref:`warnings filter <warning-filter>` will be tested against the module
+ names constructed from the path components starting from all parent
+ directories (with ``/__init__.py``, ``.py`` and, on Windows, ``.pyw``
+ stripped) and against the filename with ``.py`` stripped.
+ For example, when the filename is ``'/path/to/package/module.py'``, it will
+ be tested against ``'path.to.package.module'``, ``'to.package.module'``
+ ``'package.module'``, ``'module'``, and ``'/path/to/package/module'``.
*registry*, if supplied, should be the ``__warningregistry__`` dictionary
of the module.
.. versionchanged:: 3.6
Add the *source* parameter.
+ .. versionchanged:: next
+ If no module is passed, test the filter regular expression against
+ module names created from the path, not only the path itself.
+
.. function:: showwarning(message, category, filename, lineno, file=None, line=None)
(Contributed by Garry Cairns in :gh:`134567`.)
+warnings
+--------
+
+* Improve filtering by module in :func:`warnings.warn_explicit` if no *module*
+ argument is passed.
+ It now tests the module regular expression in the warnings filter not only
+ against the filename with ``.py`` stripped, but also against module names
+ constructed starting from different parent directories of the filename
+ (with ``/__init__.py``, ``.py`` and, on Windows, ``.pyw`` stripped).
+ (Contributed by Serhiy Storchaka in :gh:`135801`.)
+
+
venv
----
)
+def _match_filename(pattern, filename, *, MS_WINDOWS=(sys.platform == 'win32')):
+ if not filename:
+ return pattern.match('<unknown>') is not None
+ if filename[0] == '<' and filename[-1] == '>':
+ return pattern.match(filename) is not None
+
+ is_py = (filename[-3:].lower() == '.py'
+ if MS_WINDOWS else
+ filename.endswith('.py'))
+ if is_py:
+ filename = filename[:-3]
+ if pattern.match(filename): # for backward compatibility
+ return True
+ if MS_WINDOWS:
+ if not is_py and filename[-4:].lower() == '.pyw':
+ filename = filename[:-4]
+ is_py = True
+ if is_py and filename[-9:].lower() in (r'\__init__', '/__init__'):
+ filename = filename[:-9]
+ filename = filename.replace('\\', '/')
+ else:
+ if is_py and filename.endswith('/__init__'):
+ filename = filename[:-9]
+ filename = filename.replace('/', '.')
+ i = 0
+ while True:
+ if pattern.match(filename, i):
+ return True
+ i = filename.find('.', i) + 1
+ if not i:
+ return False
+
+
def warn_explicit(message, category, filename, lineno,
module=None, registry=None, module_globals=None,
source=None):
lineno = int(lineno)
- if module is None:
- module = filename or "<unknown>"
- if module[-3:].lower() == ".py":
- module = module[:-3] # XXX What about leading pathname?
if isinstance(message, Warning):
text = str(message)
category = message.__class__
else:
text = message
message = category(message)
+ modules = None
key = (text, category, lineno)
with _wm._lock:
if registry is None:
action, msg, cat, mod, ln = item
if ((msg is None or msg.match(text)) and
issubclass(category, cat) and
- (mod is None or mod.match(module)) and
- (ln == 0 or lineno == ln)):
- break
+ (ln == 0 or lineno == ln) and
+ (mod is None or (_match_filename(mod, filename)
+ if module is None else
+ mod.match(module)))):
+ break
else:
action = _wm.defaultaction
# Early exit actions
import textwrap
import types
import unittest
+import warnings
import weakref
from io import StringIO
from pathlib import Path
self.assertIsInstance(tree.body[0].value.values[0], ast.Constant)
self.assertIsInstance(tree.body[0].value.values[1], ast.Interpolation)
+ def test_filter_syntax_warnings_by_module(self):
+ filename = support.findfile('test_import/data/syntax_warnings.py')
+ with open(filename, 'rb') as f:
+ source = f.read()
+ with warnings.catch_warnings(record=True) as wlog:
+ warnings.simplefilter('error')
+ warnings.filterwarnings('always', module=r'<unknown>\z')
+ ast.parse(source)
+ self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10])
+ for wm in wlog:
+ self.assertEqual(wm.filename, '<unknown>')
+ self.assertIs(wm.category, SyntaxWarning)
+
class CopyTests(unittest.TestCase):
"""Test copying and pickling AST nodes."""
three_freevars.__globals__,
closure=my_closure)
+ def test_exec_filter_syntax_warnings_by_module(self):
+ filename = support.findfile('test_import/data/syntax_warnings.py')
+ with open(filename, 'rb') as f:
+ source = f.read()
+ with warnings.catch_warnings(record=True) as wlog:
+ warnings.simplefilter('error')
+ warnings.filterwarnings('always', module=r'<string>\z')
+ exec(source, {})
+ self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21])
+ for wm in wlog:
+ self.assertEqual(wm.filename, '<string>')
+ self.assertIs(wm.category, SyntaxWarning)
+
+ with warnings.catch_warnings(record=True) as wlog:
+ warnings.simplefilter('error')
+ warnings.filterwarnings('always', module=r'<string>\z')
+ exec(source, {'__name__': 'package.module', '__file__': filename})
+ self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21])
+ for wm in wlog:
+ self.assertEqual(wm.filename, '<string>')
+ self.assertIs(wm.category, SyntaxWarning)
+
def test_filter(self):
self.assertEqual(list(filter(lambda c: 'a' <= c <= 'z', 'Hello World')), list('elloorld'))
out, err = p.communicate()
self.assertEqual(out, b"12345678912345678912345\n")
+ def test_filter_syntax_warnings_by_module(self):
+ filename = support.findfile('test_import/data/syntax_warnings.py')
+ rc, out, err = assert_python_ok(
+ '-Werror',
+ '-Walways:::test.test_import.data.syntax_warnings',
+ filename)
+ self.assertEqual(err.count(b': SyntaxWarning: '), 6)
+
+ rc, out, err = assert_python_ok(
+ '-Werror',
+ '-Walways:::syntax_warnings',
+ filename)
+ self.assertEqual(err.count(b': SyntaxWarning: '), 6)
def tearDownModule():
self.assertEqual(wm.category, SyntaxWarning)
self.assertIn("\"is\" with 'int' literal", str(wm.message))
+ def test_filter_syntax_warnings_by_module(self):
+ filename = support.findfile('test_import/data/syntax_warnings.py')
+ with open(filename, 'rb') as f:
+ source = f.read()
+ module_re = r'test\.test_import\.data\.syntax_warnings\z'
+ with warnings.catch_warnings(record=True) as wlog:
+ warnings.simplefilter('error')
+ warnings.filterwarnings('always', module=module_re)
+ compile(source, filename, 'exec')
+ self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21])
+ for wm in wlog:
+ self.assertEqual(wm.filename, filename)
+ self.assertIs(wm.category, SyntaxWarning)
+
@support.subTests('src', [
textwrap.dedent("""
def f():
import os
import py_compile
import random
+import re
import shutil
import stat
import subprocess
import threading
import time
import types
+import warnings
import unittest
from unittest import mock
import _imp
TESTFN, rmtree, temp_umask, TESTFN_UNENCODABLE)
from test.support import script_helper
from test.support import threading_helper
-from test.test_importlib.util import uncache
+from test.test_importlib.util import uncache, temporary_pycache_prefix
from types import ModuleType
try:
import _testsinglephase
self.assertIsNotNone(cm.exception)
def test_from_import_star_invalid_type(self):
- import re
with ready_to_import() as (name, path):
with open(path, 'w', encoding='utf-8') as f:
f.write("__all__ = [b'invalid_type']")
origin = "a\x00b"
_imp.create_dynamic(Spec2())
+ def test_filter_syntax_warnings_by_module(self):
+ module_re = r'test\.test_import\.data\.syntax_warnings\z'
+ unload('test.test_import.data.syntax_warnings')
+ with (os_helper.temp_dir() as tmpdir,
+ temporary_pycache_prefix(tmpdir),
+ warnings.catch_warnings(record=True) as wlog):
+ warnings.simplefilter('error')
+ warnings.filterwarnings('always', module=module_re)
+ import test.test_import.data.syntax_warnings
+ self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21])
+ filename = test.test_import.data.syntax_warnings.__file__
+ for wm in wlog:
+ self.assertEqual(wm.filename, filename)
+ self.assertIs(wm.category, SyntaxWarning)
+
+ module_re = r'syntax_warnings\z'
+ unload('test.test_import.data.syntax_warnings')
+ with (os_helper.temp_dir() as tmpdir,
+ temporary_pycache_prefix(tmpdir),
+ warnings.catch_warnings(record=True) as wlog):
+ warnings.simplefilter('error')
+ warnings.filterwarnings('always', module=module_re)
+ import test.test_import.data.syntax_warnings
+ self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10, 13, 14, 21])
+ filename = test.test_import.data.syntax_warnings.__file__
+ for wm in wlog:
+ self.assertEqual(wm.filename, filename)
+ self.assertIs(wm.category, SyntaxWarning)
+
@skip_if_dont_write_bytecode
class FilePermissionTests(unittest.TestCase):
--- /dev/null
+# Syntax warnings emitted in different parts of the Python compiler.
+
+# Parser/lexer/lexer.c
+x = 1or 0 # line 4
+
+# Parser/tokenizer/helpers.c
+'\z' # line 7
+
+# Parser/string_parser.c
+'\400' # line 10
+
+# _PyCompile_Warn() in Python/codegen.c
+assert(x, 'message') # line 13
+x is 1 # line 14
+
+# _PyErr_EmitSyntaxWarning() in Python/ast_preprocess.c
+def f():
+ try:
+ pass
+ finally:
+ return 42 # line 21
import re
import textwrap
import symtable
+import warnings
import unittest
from test import support
# check error path when 'compile_type' AC conversion failed
self.assertRaises(TypeError, symtable.symtable, '', mortal_str, 1)
+ def test_filter_syntax_warnings_by_module(self):
+ filename = support.findfile('test_import/data/syntax_warnings.py')
+ with open(filename, 'rb') as f:
+ source = f.read()
+ module_re = r'test\.test_import\.data\.syntax_warnings\z'
+ with warnings.catch_warnings(record=True) as wlog:
+ warnings.simplefilter('error')
+ warnings.filterwarnings('always', module=module_re)
+ symtable.symtable(source, filename, 'exec')
+ self.assertEqual(sorted(wm.lineno for wm in wlog), [4, 7, 10])
+ for wm in wlog:
+ self.assertEqual(wm.filename, filename)
+ self.assertIs(wm.category, SyntaxWarning)
+
class ComprehensionTests(unittest.TestCase):
def get_identifiers_recursive(self, st, res):
self.module.warn_explicit('msg', UserWarning, 'filename', 42,
module='package.module')
self.assertEqual(len(w), 1)
+ self.module.warn_explicit('msg', UserWarning, '/path/to/package/module', 42)
+ self.assertEqual(len(w), 2)
+ self.module.warn_explicit('msg', UserWarning, '/path/to/package/module.py', 42)
+ self.assertEqual(len(w), 3)
+ self.module.warn_explicit('msg', UserWarning, '/path/to/package/module/__init__.py', 42)
+ self.assertEqual(len(w), 4)
with self.assertRaises(UserWarning):
- self.module.warn_explicit('msg', UserWarning, '/path/to/package/module', 42)
- with self.assertRaises(UserWarning):
- self.module.warn_explicit('msg', UserWarning, '/path/to/package/module.py', 42)
+ self.module.warn_explicit('msg', UserWarning, '/path/to/package/module/__init__', 42)
+ if MS_WINDOWS:
+ self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PY', 42)
+ self.assertEqual(len(w), 5)
+ self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module\__INIT__.PY', 42)
+ self.assertEqual(len(w), 6)
+ self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PYW', 42)
+ self.assertEqual(len(w), 7)
+ self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module\__INIT__.PYW', 42)
+ self.assertEqual(len(w), 8)
with self.module.catch_warnings(record=True) as w:
self.module.simplefilter('error')
with self.assertRaises(UserWarning):
self.module.warn_explicit('msg', UserWarning, '/PATH/TO/PACKAGE/MODULE', 42)
if MS_WINDOWS:
- if self.module is py_warnings:
- self.module.warn_explicit('msg', UserWarning, r'/path/to/package/module.PY', 42)
- self.assertEqual(len(w), 3)
+ self.module.warn_explicit('msg', UserWarning, r'/path/to/package/module.PY', 42)
+ self.assertEqual(len(w), 3)
with self.assertRaises(UserWarning):
self.module.warn_explicit('msg', UserWarning, r'/path/to/package/module/__init__.py', 42)
with self.assertRaises(UserWarning):
self.assertEqual(len(w), 1)
self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.py', 42)
self.assertEqual(len(w), 2)
- if self.module is py_warnings:
- self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PY', 42)
- self.assertEqual(len(w), 3)
+ self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.PY', 42)
+ self.assertEqual(len(w), 3)
with self.assertRaises(UserWarning):
self.module.warn_explicit('msg', UserWarning, r'C:\path\to\package\module.pyw', 42)
with self.assertRaises(UserWarning):
def test_mutate_filter_list(self):
class X:
- def match(self, a):
+ def match(self, a, start=0):
L[:] = []
L = [("default",X(),UserWarning,X(),0) for i in range(2)]
--- /dev/null
+Improve filtering by module in :func:`warnings.warn_explicit` if no *module*
+argument is passed. It now tests the module regular expression in the
+warnings filter not only against the filename with ``.py`` stripped, but
+also against module names constructed starting from different parent
+directories of the filename (with ``/__init__.py``, ``.py`` and, on Windows,
+``.pyw`` stripped).
/*************************************************************************/
static int
-check_matched(PyInterpreterState *interp, PyObject *obj, PyObject *arg)
+check_matched(PyInterpreterState *interp, PyObject *obj, PyObject *arg, PyObject *arg2)
{
PyObject *result;
int rc;
/* An internal plain text default filter must match exactly */
if (PyUnicode_CheckExact(obj)) {
+ if (arg == NULL) {
+ return 0;
+ }
int cmp_result = PyUnicode_Compare(obj, arg);
if (cmp_result == -1 && PyErr_Occurred()) {
return -1;
}
/* Otherwise assume a regex filter and call its match() method */
- result = PyObject_CallMethodOneArg(obj, &_Py_ID(match), arg);
+ if (arg != NULL) {
+ result = PyObject_CallMethodOneArg(obj, &_Py_ID(match), arg);
+ }
+ else {
+ PyObject *match = PyImport_ImportModuleAttrString("_py_warnings", "_match_filename");
+ if (match == NULL) {
+ return -1;
+ }
+ result = PyObject_CallFunctionObjArgs(match, obj, arg2, NULL);
+ Py_DECREF(match);
+ }
if (result == NULL)
return -1;
-
rc = PyObject_IsTrue(result);
Py_DECREF(result);
return rc;
static bool
filter_search(PyInterpreterState *interp, PyObject *category,
PyObject *text, Py_ssize_t lineno,
- PyObject *module, char *list_name, PyObject *filters,
+ PyObject *module, PyObject *filename, char *list_name, PyObject *filters,
PyObject **item, PyObject **matched_action) {
bool result = true;
*matched_action = NULL;
break;
}
- good_msg = check_matched(interp, msg, text);
+ good_msg = check_matched(interp, msg, text, NULL);
if (good_msg == -1) {
Py_DECREF(tmp_item);
result = false;
break;
}
- good_mod = check_matched(interp, mod, module);
+ good_mod = check_matched(interp, mod, module, filename);
if (good_mod == -1) {
Py_DECREF(tmp_item);
result = false;
static PyObject*
get_filter(PyInterpreterState *interp, PyObject *category,
PyObject *text, Py_ssize_t lineno,
- PyObject *module, PyObject **item)
+ PyObject *module, PyObject *filename, PyObject **item)
{
#ifdef Py_DEBUG
WarningsState *st = warnings_get_state(interp);
use_global_filters = true;
} else {
PyObject *context_action = NULL;
- if (!filter_search(interp, category, text, lineno, module, "_warnings_context _filters",
+ if (!filter_search(interp, category, text, lineno, module, filename, "_warnings_context _filters",
context_filters, item, &context_action)) {
Py_DECREF(context_filters);
return NULL;
if (filters == NULL) {
return NULL;
}
- if (!filter_search(interp, category, text, lineno, module, "filters",
+ if (!filter_search(interp, category, text, lineno, module, filename, "filters",
filters, item, &action)) {
return NULL;
}
return 0;
}
-/* New reference. */
-static PyObject *
-normalize_module(PyObject *filename)
-{
- PyObject *module;
- int kind;
- const void *data;
- Py_ssize_t len;
-
- len = PyUnicode_GetLength(filename);
- if (len < 0)
- return NULL;
-
- if (len == 0)
- return PyUnicode_FromString("<unknown>");
-
- kind = PyUnicode_KIND(filename);
- data = PyUnicode_DATA(filename);
-
- /* if filename.endswith(".py"): */
- if (len >= 3 &&
- PyUnicode_READ(kind, data, len-3) == '.' &&
- PyUnicode_READ(kind, data, len-2) == 'p' &&
- PyUnicode_READ(kind, data, len-1) == 'y')
- {
- module = PyUnicode_Substring(filename, 0, len-3);
- }
- else {
- module = Py_NewRef(filename);
- }
- return module;
-}
-
static int
update_registry(PyInterpreterState *interp, PyObject *registry, PyObject *text,
PyObject *category, int add_zero)
return NULL;
}
- /* Normalize module. */
- if (module == NULL) {
- module = normalize_module(filename);
- if (module == NULL)
- return NULL;
- }
- else
- Py_INCREF(module);
-
/* Normalize message. */
Py_INCREF(message); /* DECREF'ed in cleanup. */
if (PyObject_TypeCheck(message, (PyTypeObject *)PyExc_Warning)) {
/* Else this warning hasn't been generated before. */
}
- action = get_filter(interp, category, text, lineno, module, &item);
+ action = get_filter(interp, category, text, lineno, module, filename, &item);
if (action == NULL)
goto cleanup;
Py_XDECREF(key);
Py_XDECREF(text);
Py_XDECREF(lineno_obj);
- Py_DECREF(module);
Py_XDECREF(message);
return result; /* Py_None or NULL. */
}