Skip to content

Commit f74f818

Browse files
authored
Add option to selectively disable deprecation warnings (#18641)
Suggested in #18192 (comment) Fixes #18435
1 parent 653fc9b commit f74f818

File tree

8 files changed

+104
-0
lines changed

8 files changed

+104
-0
lines changed

Diff for: docs/source/command_line.rst

+20
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,26 @@ potentially problematic or redundant in some way.
556556
notes, causing mypy to eventually finish with a zero exit code. Features
557557
are considered deprecated when decorated with ``warnings.deprecated``.
558558

559+
.. option:: --deprecated-calls-exclude
560+
561+
This flag allows to selectively disable :ref:`deprecated<code-deprecated>` warnings
562+
for functions and methods defined in specific packages, modules, or classes.
563+
Note that each exclude entry acts as a prefix. For example (assuming ``foo.A.func`` is deprecated):
564+
565+
.. code-block:: python
566+
567+
# mypy --enable-error-code deprecated
568+
# --deprecated-calls-exclude=foo.A
569+
import foo
570+
571+
foo.A().func() # OK, the deprecated warning is ignored
572+
573+
# file foo.py
574+
from typing_extensions import deprecated
575+
class A:
576+
@deprecated("Use A.func2 instead")
577+
def func(self): pass
578+
559579
.. _miscellaneous-strictness-flags:
560580

561581
Miscellaneous strictness flags

Diff for: docs/source/config_file.rst

+10
Original file line numberDiff line numberDiff line change
@@ -666,6 +666,16 @@ section of the command line docs.
666666
Shows a warning when encountering any code inferred to be unreachable or
667667
redundant after performing type analysis.
668668

669+
.. confval:: deprecated_calls_exclude
670+
671+
:type: comma-separated list of strings
672+
673+
Selectively excludes functions and methods defined in specific packages,
674+
modules, and classes from the :ref:`deprecated<code-deprecated>` error code.
675+
This also applies to all submodules of packages (i.e. everything inside
676+
a given prefix). Note, this option does not support per-file configuration,
677+
the exclusions list is defined globally for all your code.
678+
669679

670680
Suppressing errors
671681
******************

Diff for: docs/source/error_code_list2.rst

+2
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ locally. Features are considered deprecated when decorated with ``warnings.depr
243243
specified in `PEP 702 <https://peps.python.org/pep-0702>`_.
244244
Use the :option:`--report-deprecated-as-note <mypy --report-deprecated-as-note>` option to
245245
turn all such errors into notes.
246+
Use :option:`--deprecated-calls-exclude <mypy --deprecated-calls-exclude>` to hide warnings
247+
for specific functions, classes and packages.
246248

247249
.. note::
248250

Diff for: mypy/checker.py

+4
Original file line numberDiff line numberDiff line change
@@ -7865,6 +7865,10 @@ def warn_deprecated(self, node: Node | None, context: Context) -> None:
78657865
isinstance(node, (FuncDef, OverloadedFuncDef, TypeInfo))
78667866
and ((deprecated := node.deprecated) is not None)
78677867
and not self.is_typeshed_stub
7868+
and not any(
7869+
node.fullname == p or node.fullname.startswith(f"{p}.")
7870+
for p in self.options.deprecated_calls_exclude
7871+
)
78687872
):
78697873
warn = self.msg.note if self.options.report_deprecated_as_note else self.msg.fail
78707874
warn(deprecated, context, code=codes.DEPRECATED)

Diff for: mypy/main.py

+9
Original file line numberDiff line numberDiff line change
@@ -826,6 +826,14 @@ def add_invertible_flag(
826826
help="Report importing or using deprecated features as notes instead of errors",
827827
group=lint_group,
828828
)
829+
lint_group.add_argument(
830+
"--deprecated-calls-exclude",
831+
metavar="MODULE",
832+
action="append",
833+
default=[],
834+
help="Disable deprecated warnings for functions/methods coming"
835+
" from specific package, module, or class",
836+
)
829837

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

13711379
validate_package_allow_list(options.untyped_calls_exclude)
1380+
validate_package_allow_list(options.deprecated_calls_exclude)
13721381

13731382
options.process_error_codes(error_callback=parser.error)
13741383
options.process_incomplete_features(error_callback=parser.error, warning_callback=print)

Diff for: mypy/options.py

+4
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ def __init__(self) -> None:
182182
# Report importing or using deprecated features as errors instead of notes.
183183
self.report_deprecated_as_note = False
184184

185+
# Allow deprecated calls from function coming from modules/packages
186+
# in this list (each item effectively acts as a prefix match)
187+
self.deprecated_calls_exclude: list[str] = []
188+
185189
# Warn about unused '# type: ignore' comments
186190
self.warn_unused_ignores = False
187191

Diff for: mypy/typeanal.py

+4
Original file line numberDiff line numberDiff line change
@@ -823,6 +823,10 @@ def check_and_warn_deprecated(self, info: TypeInfo, ctx: Context) -> None:
823823
(deprecated := info.deprecated)
824824
and not self.is_typeshed_stub
825825
and not (self.api.type and (self.api.type.fullname == info.fullname))
826+
and not any(
827+
info.fullname == p or info.fullname.startswith(f"{p}.")
828+
for p in self.options.deprecated_calls_exclude
829+
)
826830
):
827831
for imp in self.cur_mod_node.imports:
828832
if isinstance(imp, ImportFrom) and any(info.name == n[0] for n in imp.names):

Diff for: test-data/unit/check-deprecated.test

+51
Original file line numberDiff line numberDiff line change
@@ -797,5 +797,56 @@ def g(x: int) -> int: ...
797797
@overload
798798
def g(x: str) -> str: ...
799799
def g(x: Union[int, str]) -> Union[int, str]: ...
800+
[builtins fixtures/tuple.pyi]
801+
802+
[case testDeprecatedExclude]
803+
# 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__
804+
from m import C, D, E
805+
806+
[file m.py]
807+
from typing import Union, overload
808+
from typing_extensions import deprecated
809+
810+
@deprecated("use C2 instead")
811+
class C:
812+
def __init__(self) -> None: ...
813+
814+
c: C
815+
C()
816+
C.__init__(c)
817+
818+
class D:
819+
@deprecated("use D.g instead")
820+
def f(self) -> None: ...
821+
822+
def g(self) -> None: ...
823+
824+
D.f
825+
D().f
826+
D().f()
827+
828+
class E:
829+
@overload
830+
def f(self, x: int) -> int: ...
831+
@overload
832+
def f(self, x: str) -> str: ...
833+
@deprecated("use E.f2 instead")
834+
def f(self, x: Union[int, str]) -> Union[int, str]: ...
835+
836+
@deprecated("use E.h instead")
837+
def g(self) -> None: ...
838+
839+
@overload
840+
@deprecated("no A + int")
841+
def __add__(self, v: int) -> None: ...
842+
@overload
843+
def __add__(self, v: str) -> None: ...
844+
def __add__(self, v: Union[int, str]) -> None: ...
845+
846+
E().f(1)
847+
E().f("x")
800848

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

0 commit comments

Comments
 (0)