summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/test/test_warnings/__init__.py39
-rw-r--r--Lib/test/test_warnings/data/package_helper.py10
-rw-r--r--Lib/test/test_warnings/data/stacklevel.py8
-rw-r--r--Lib/warnings.py29
4 files changed, 77 insertions, 9 deletions
diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py
index 9e473e9..9e680c8 100644
--- a/Lib/test/test_warnings/__init__.py
+++ b/Lib/test/test_warnings/__init__.py
@@ -12,6 +12,7 @@ from test.support import os_helper
from test.support import warnings_helper
from test.support.script_helper import assert_python_ok, assert_python_failure
+from test.test_warnings.data import package_helper
from test.test_warnings.data import stacklevel as warning_tests
import warnings as original_warnings
@@ -472,6 +473,42 @@ class WarnTests(BaseTest):
self.assertEqual(len(w), 1)
self.assertEqual(w[0].filename, __file__)
+ def test_skip_file_prefixes(self):
+ with warnings_state(self.module):
+ with original_warnings.catch_warnings(record=True,
+ module=self.module) as w:
+ self.module.simplefilter('always')
+
+ # Warning never attributed to the data/ package.
+ package_helper.inner_api(
+ "inner_api", stacklevel=2,
+ warnings_module=warning_tests.warnings)
+ self.assertEqual(w[-1].filename, __file__)
+ warning_tests.package("package api", stacklevel=2)
+ self.assertEqual(w[-1].filename, __file__)
+ self.assertEqual(w[-2].filename, w[-1].filename)
+ # Low stacklevels are overridden to 2 behavior.
+ warning_tests.package("package api 1", stacklevel=1)
+ self.assertEqual(w[-1].filename, __file__)
+ warning_tests.package("package api 0", stacklevel=0)
+ self.assertEqual(w[-1].filename, __file__)
+ warning_tests.package("package api -99", stacklevel=-99)
+ self.assertEqual(w[-1].filename, __file__)
+
+ # The stacklevel still goes up out of the package.
+ warning_tests.package("prefix02", stacklevel=3)
+ self.assertIn("unittest", w[-1].filename)
+
+ def test_skip_file_prefixes_type_errors(self):
+ with warnings_state(self.module):
+ warn = warning_tests.warnings.warn
+ with self.assertRaises(TypeError):
+ warn("msg", skip_file_prefixes=[])
+ with self.assertRaises(TypeError):
+ warn("msg", skip_file_prefixes=(b"bytes",))
+ with self.assertRaises(TypeError):
+ warn("msg", skip_file_prefixes="a sequence of strs")
+
def test_exec_filename(self):
filename = "<warnings-test>"
codeobj = compile(("import warnings\n"
@@ -895,7 +932,7 @@ class WarningsDisplayTests(BaseTest):
message = "msg"
category = Warning
file_name = os.path.splitext(warning_tests.__file__)[0] + '.py'
- line_num = 3
+ line_num = 5
file_line = linecache.getline(file_name, line_num).strip()
format = "%s:%s: %s: %s\n %s\n"
expect = format % (file_name, line_num, category.__name__, message,
diff --git a/Lib/test/test_warnings/data/package_helper.py b/Lib/test/test_warnings/data/package_helper.py
new file mode 100644
index 0000000..c22a4f6
--- /dev/null
+++ b/Lib/test/test_warnings/data/package_helper.py
@@ -0,0 +1,10 @@
+# helper to the helper for testing skip_file_prefixes.
+
+import os
+
+package_path = os.path.dirname(__file__)
+
+def inner_api(message, *, stacklevel, warnings_module):
+ warnings_module.warn(
+ message, stacklevel=stacklevel,
+ skip_file_prefixes=(package_path,))
diff --git a/Lib/test/test_warnings/data/stacklevel.py b/Lib/test/test_warnings/data/stacklevel.py
index d0519ef..c6dd247 100644
--- a/Lib/test/test_warnings/data/stacklevel.py
+++ b/Lib/test/test_warnings/data/stacklevel.py
@@ -1,9 +1,15 @@
-# Helper module for testing the skipmodules argument of warnings.warn()
+# Helper module for testing stacklevel and skip_file_prefixes arguments
+# of warnings.warn()
import warnings
+from test.test_warnings.data import package_helper
def outer(message, stacklevel=1):
inner(message, stacklevel)
def inner(message, stacklevel=1):
warnings.warn(message, stacklevel=stacklevel)
+
+def package(message, *, stacklevel):
+ package_helper.inner_api(message, stacklevel=stacklevel,
+ warnings_module=warnings)
diff --git a/Lib/warnings.py b/Lib/warnings.py
index 7d8c440..98ae708 100644
--- a/Lib/warnings.py
+++ b/Lib/warnings.py
@@ -269,22 +269,32 @@ def _getcategory(category):
return cat
+def _is_internal_filename(filename):
+ return 'importlib' in filename and '_bootstrap' in filename
+
+
+def _is_filename_to_skip(filename, skip_file_prefixes):
+ return any(filename.startswith(prefix) for prefix in skip_file_prefixes)
+
+
def _is_internal_frame(frame):
"""Signal whether the frame is an internal CPython implementation detail."""
- filename = frame.f_code.co_filename
- return 'importlib' in filename and '_bootstrap' in filename
+ return _is_internal_filename(frame.f_code.co_filename)
-def _next_external_frame(frame):
- """Find the next frame that doesn't involve CPython internals."""
+def _next_external_frame(frame, skip_file_prefixes):
+ """Find the next frame that doesn't involve Python or user internals."""
frame = frame.f_back
- while frame is not None and _is_internal_frame(frame):
+ while frame is not None and (
+ _is_internal_filename(filename := frame.f_code.co_filename) or
+ _is_filename_to_skip(filename, skip_file_prefixes)):
frame = frame.f_back
return frame
# Code typically replaced by _warnings
-def warn(message, category=None, stacklevel=1, source=None):
+def warn(message, category=None, stacklevel=1, source=None,
+ *, skip_file_prefixes=()):
"""Issue a warning, or maybe ignore it or raise an exception."""
# Check if message is already a Warning object
if isinstance(message, Warning):
@@ -295,6 +305,11 @@ def warn(message, category=None, stacklevel=1, source=None):
if not (isinstance(category, type) and issubclass(category, Warning)):
raise TypeError("category must be a Warning subclass, "
"not '{:s}'".format(type(category).__name__))
+ if not isinstance(skip_file_prefixes, tuple):
+ # The C version demands a tuple for implementation performance.
+ raise TypeError('skip_file_prefixes must be a tuple of strs.')
+ if skip_file_prefixes:
+ stacklevel = max(2, stacklevel)
# Get context information
try:
if stacklevel <= 1 or _is_internal_frame(sys._getframe(1)):
@@ -305,7 +320,7 @@ def warn(message, category=None, stacklevel=1, source=None):
frame = sys._getframe(1)
# Look for one frame less since the above line starts us off.
for x in range(stacklevel-1):
- frame = _next_external_frame(frame)
+ frame = _next_external_frame(frame, skip_file_prefixes)
if frame is None:
raise ValueError
except ValueError: