diff --git a/Doc/library/warnings.rst b/Doc/library/warnings.rst index d5f89102d5853bb..781a0b98a1289da 100644 --- a/Doc/library/warnings.rst +++ b/Doc/library/warnings.rst @@ -566,7 +566,7 @@ Available Functions and calls to :func:`simplefilter`. -.. decorator:: deprecated(message, /, *, category=DeprecationWarning, stacklevel=1) +.. decorator:: deprecated(message, /, *, category=DeprecationWarning, stacklevel=1, skip_file_prefixes=()) Decorator to indicate that a class, function or overload is deprecated. @@ -598,12 +598,14 @@ Available Functions on use of deprecated objects. For functions, that happens on calls; for classes, on instantiation and on creation of subclasses. If the *category* is ``None``, no warning is emitted at runtime. - The *stacklevel* determines where the - warning is emitted. If it is ``1`` (the default), the warning + The *stacklevel* and *skip_file_prefixes* entries determine where the + warning is emitted. If *stacklevel* is ``1`` (the default), the warning is emitted at the direct caller of the deprecated object; if it is higher, it is emitted further up the stack. - Static type checker behavior is not affected by the *category* - and *stacklevel* arguments. + Frames in files whose path starts with any of the strings in + *skip_file_prefixes* are skipped. + Static type checker behavior is not affected by the *category*, + *skip_file_prefixes*, and *stacklevel* arguments. The deprecation message passed to the decorator is saved in the ``__deprecated__`` attribute on the decorated object. @@ -615,6 +617,9 @@ Available Functions .. versionadded:: 3.13 See :pep:`702`. + .. versionchanged:: 3.16 + Added *skip_file_prefixes*. + Available Context Managers -------------------------- diff --git a/Lib/_py_warnings.py b/Lib/_py_warnings.py index ab09913de6812dd..81d55cb0bda186c 100644 --- a/Lib/_py_warnings.py +++ b/Lib/_py_warnings.py @@ -3,6 +3,7 @@ import sys import _contextvars import _thread +lazy from collections.abc import Iterable __all__ = ["warn", "warn_explicit", "showwarning", @@ -763,12 +764,14 @@ def g(x: str) -> int: ... on use of deprecated objects. For functions, that happens on calls; for classes, on instantiation and on creation of subclasses. If the *category* is ``None``, no warning is emitted at runtime. - The *stacklevel* determines where the - warning is emitted. If it is ``1`` (the default), the warning + The *stacklevel* and *skip_file_prefixes* entries determine where the + warning is emitted. If *stacklevel* is ``1`` (the default), the warning is emitted at the direct caller of the deprecated object; if it is higher, it is emitted further up the stack. - Static type checker behavior is not affected by the *category* - and *stacklevel* arguments. + Frames in files whose path starts with any of the strings in + *skip_file_prefixes* are skipped. + Static type checker behavior is not affected by the *category*, + *skip_file_prefixes*, and *stacklevel* arguments. The deprecation message passed to the decorator is saved in the ``__deprecated__`` attribute on the decorated object. @@ -786,6 +789,7 @@ def __init__( *, category: type[Warning] | None = DeprecationWarning, stacklevel: int = 1, + skip_file_prefixes: Iterable[str] = (), ) -> None: if not isinstance(message, str): raise TypeError( @@ -794,6 +798,7 @@ def __init__( self.message = message self.category = category self.stacklevel = stacklevel + self.skip_file_prefixes = tuple(skip_file_prefixes) def __call__(self, arg, /): # Make sure the inner functions created below don't @@ -813,7 +818,7 @@ def __call__(self, arg, /): @functools.wraps(original_new) def __new__(cls, /, *args, **kwargs): if cls is arg: - _wm.warn(msg, category=category, stacklevel=stacklevel + 1) + _wm.warn(msg, category=category, stacklevel=stacklevel + 1, skip_file_prefixes=self.skip_file_prefixes) if original_new is not object.__new__: return original_new(cls, *args, **kwargs) # Mirrors a similar check in object.__new__. @@ -837,11 +842,11 @@ def __new__(cls, /, *args, **kwargs): @functools.wraps(original_init_subclass) def __init_subclass__(*args, **kwargs): - _wm.warn(msg, category=category, stacklevel=stacklevel + 1) + _wm.warn(msg, category=category, stacklevel=stacklevel + 1, skip_file_prefixes=self.skip_file_prefixes) return original_init_subclass(*args, **kwargs) else: def __init_subclass__(cls, *args, **kwargs): - _wm.warn(msg, category=category, stacklevel=stacklevel + 1) + _wm.warn(msg, category=category, stacklevel=stacklevel + 1, skip_file_prefixes=self.skip_file_prefixes) return super(arg, cls).__init_subclass__(*args, **kwargs) arg.__init_subclass__ = classmethod(__init_subclass__) @@ -855,7 +860,7 @@ def __init_subclass__(cls, *args, **kwargs): @functools.wraps(arg) def wrapper(*args, **kwargs): - _wm.warn(msg, category=category, stacklevel=stacklevel + 1) + _wm.warn(msg, category=category, stacklevel=stacklevel + 1, skip_file_prefixes=self.skip_file_prefixes) return arg(*args, **kwargs) if inspect.iscoroutinefunction(arg): diff --git a/Lib/test/test_warnings/__init__.py b/Lib/test/test_warnings/__init__.py index 5b5d5c2f70ffd7a..52c5b157d324380 100644 --- a/Lib/test/test_warnings/__init__.py +++ b/Lib/test/test_warnings/__init__.py @@ -2252,6 +2252,25 @@ def c(): with self.assertWarnsRegex(RuntimeWarning, "c will go away soon"): c() + def test_skip_file_prefixes(self): + code = """\ +from warnings import deprecated + +@deprecated("good as gone", skip_file_prefixes=(__file__,)) +def nested_func() -> Outer: + pass +""" + mod = types.ModuleType("mod") + mod.__file__ = "/fictional/path/my_test_file.py" + exec(code, mod.__dict__) + + with py_warnings.catch_warnings(record=True) as record: + py_warnings.simplefilter("always") + lineno_expected = sys._getframe().f_lineno + 1 # next line + mod.nested_func() + + assert (record[0].filename, record[0].lineno) == (__file__, lineno_expected) + def test_turn_off_warnings(self): @deprecated("d will go away soon", category=None) def d(): diff --git a/Misc/NEWS.d/next/Library/2026-09-04-14-06-30.gh-issue-156929.6eTccv.rst b/Misc/NEWS.d/next/Library/2026-09-04-14-06-30.gh-issue-156929.6eTccv.rst new file mode 100644 index 000000000000000..1ff7e43bfe5fbda --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-04-14-06-30.gh-issue-156929.6eTccv.rst @@ -0,0 +1 @@ +Add ``skip_file_prefixes`` parameter to :deco:`warnings.deprecated`, mirroring the parameter of the same name for :func:`warnings.warn`.