diff options
author | Gregory P. Smith <greg@krypto.org> | 2023-01-28 02:35:14 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-28 02:35:14 (GMT) |
commit | 052f53d65d9f65c7c3223a383857ad07a182c2d7 (patch) | |
tree | 4d0a6ee4c3026972d1bb01017e50ef8072cd1fe1 /Lib/warnings.py | |
parent | 8cef9c0f92720f6810be1c74e00f611acb4b8b1e (diff) | |
download | cpython-052f53d65d9f65c7c3223a383857ad07a182c2d7.zip cpython-052f53d65d9f65c7c3223a383857ad07a182c2d7.tar.gz cpython-052f53d65d9f65c7c3223a383857ad07a182c2d7.tar.bz2 |
gh-39615: Add warnings.warn() skip_file_prefixes support (#100840)
`warnings.warn()` gains the ability to skip stack frames based on code
filename prefix rather than only a numeric `stacklevel=` via a new
`skip_file_prefixes=` keyword argument.
Diffstat (limited to 'Lib/warnings.py')
-rw-r--r-- | Lib/warnings.py | 29 |
1 files changed, 22 insertions, 7 deletions
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: |