Skip to content

Add option to selectively disable deprecation warnings #18641

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions docs/source/command_line.rst
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,26 @@ potentially problematic or redundant in some way.
notes, causing mypy to eventually finish with a zero exit code. Features
are considered deprecated when decorated with ``warnings.deprecated``.

.. option:: --deprecated-calls-exclude

This flag allows to selectively disable :ref:`deprecated<code-deprecated>` warnings
for functions and methods defined in specific packages, modules, or classes.
Note that each exclude entry acts as a prefix. For example (assuming ``foo.A.func`` is deprecated):

.. code-block:: python

# mypy --enable-error-code deprecated
# --deprecated-calls-exclude=foo.A
import foo

foo.A().func() # OK, the deprecated warning is ignored

# file foo.py
from typing_extensions import deprecated
class A:
@deprecated("Use A.func2 instead")
def func(self): pass

.. _miscellaneous-strictness-flags:

Miscellaneous strictness flags
Expand Down
10 changes: 10 additions & 0 deletions docs/source/config_file.rst
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,16 @@ section of the command line docs.
Shows a warning when encountering any code inferred to be unreachable or
redundant after performing type analysis.

.. confval:: deprecated_calls_exclude

:type: comma-separated list of strings

Selectively excludes functions and methods defined in specific packages,
modules, and classes from the :ref:`deprecated<code-deprecated>` error code.
This also applies to all submodules of packages (i.e. everything inside
a given prefix). Note, this option does not support per-file configuration,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe end with ";" instead of ","?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tbh I mostly copied the contents from the untyped_calls_exclude option. So I'd leave it as is. https://mypy.readthedocs.io/en/stable/config_file.html#confval-untyped_calls_exclude

the exclusions list is defined globally for all your code.


Suppressing errors
******************
Expand Down
2 changes: 2 additions & 0 deletions docs/source/error_code_list2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,8 @@ locally. Features are considered deprecated when decorated with ``warnings.depr
specified in `PEP 702 <https://peps.python.org/pep-0702>`_.
Use the :option:`--report-deprecated-as-note <mypy --report-deprecated-as-note>` option to
turn all such errors into notes.
Use :option:`--deprecated-calls-exclude <mypy --deprecated-calls-exclude>` to hide warnings
for specific functions, classes and packages.

.. note::

Expand Down
4 changes: 4 additions & 0 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -7865,6 +7865,10 @@ def warn_deprecated(self, node: Node | None, context: Context) -> None:
isinstance(node, (FuncDef, OverloadedFuncDef, TypeInfo))
and ((deprecated := node.deprecated) is not None)
and not self.is_typeshed_stub
and not any(
node.fullname == p or node.fullname.startswith(f"{p}.")
for p in self.options.deprecated_calls_exclude
)
):
warn = self.msg.note if self.options.report_deprecated_as_note else self.msg.fail
warn(deprecated, context, code=codes.DEPRECATED)
Expand Down
9 changes: 9 additions & 0 deletions mypy/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,14 @@ def add_invertible_flag(
help="Report importing or using deprecated features as notes instead of errors",
group=lint_group,
)
lint_group.add_argument(
"--deprecated-calls-exclude",
metavar="MODULE",
action="append",
default=[],
help="Disable deprecated warnings for functions/methods coming"
" from specific package, module, or class",
)

# Note: this group is intentionally added here even though we don't add
# --strict to this group near the end.
Expand Down Expand Up @@ -1369,6 +1377,7 @@ def set_strict_flags() -> None:
)

validate_package_allow_list(options.untyped_calls_exclude)
validate_package_allow_list(options.deprecated_calls_exclude)

options.process_error_codes(error_callback=parser.error)
options.process_incomplete_features(error_callback=parser.error, warning_callback=print)
Expand Down
4 changes: 4 additions & 0 deletions mypy/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ def __init__(self) -> None:
# Report importing or using deprecated features as errors instead of notes.
self.report_deprecated_as_note = False

# Allow deprecated calls from function coming from modules/packages
# in this list (each item effectively acts as a prefix match)
self.deprecated_calls_exclude: list[str] = []

# Warn about unused '# type: ignore' comments
self.warn_unused_ignores = False

Expand Down
4 changes: 4 additions & 0 deletions mypy/typeanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -823,6 +823,10 @@ def check_and_warn_deprecated(self, info: TypeInfo, ctx: Context) -> None:
(deprecated := info.deprecated)
and not self.is_typeshed_stub
and not (self.api.type and (self.api.type.fullname == info.fullname))
and not any(
info.fullname == p or info.fullname.startswith(f"{p}.")
for p in self.options.deprecated_calls_exclude
)
):
for imp in self.cur_mod_node.imports:
if isinstance(imp, ImportFrom) and any(info.name == n[0] for n in imp.names):
Expand Down
51 changes: 51 additions & 0 deletions test-data/unit/check-deprecated.test
Original file line number Diff line number Diff line change
Expand Up @@ -797,5 +797,56 @@ def g(x: int) -> int: ...
@overload
def g(x: str) -> str: ...
def g(x: Union[int, str]) -> Union[int, str]: ...
[builtins fixtures/tuple.pyi]

[case testDeprecatedExclude]
# flags: --enable-error-code=deprecated --deprecated-calls-exclude=m.C --deprecated-calls-exclude=m.D --deprecated-calls-exclude=m.E.f --deprecated-calls-exclude=m.E.g --deprecated-calls-exclude=m.E.__add__
from m import C, D, E

[file m.py]
from typing import Union, overload
from typing_extensions import deprecated

@deprecated("use C2 instead")
class C:
def __init__(self) -> None: ...

c: C
C()
C.__init__(c)

class D:
@deprecated("use D.g instead")
def f(self) -> None: ...

def g(self) -> None: ...

D.f
D().f
D().f()

class E:
@overload
def f(self, x: int) -> int: ...
@overload
def f(self, x: str) -> str: ...
@deprecated("use E.f2 instead")
def f(self, x: Union[int, str]) -> Union[int, str]: ...

@deprecated("use E.h instead")
def g(self) -> None: ...

@overload
@deprecated("no A + int")
def __add__(self, v: int) -> None: ...
@overload
def __add__(self, v: str) -> None: ...
def __add__(self, v: Union[int, str]) -> None: ...

E().f(1)
E().f("x")

e = E()
e.g()
e + 1
[builtins fixtures/tuple.pyi]