Skip to content

Option to report deprecated only for symbols defined in selected packages (inverse of deprecated_calls_exclude) #22026

Description

@llucax

Feature

Add a way to report [deprecated] only for deprecated symbols defined in selected packages, the inverse of deprecated_calls_exclude:

[tool.mypy]
enable_error_code = ["deprecated"]
deprecated_calls_include = ["mylib"]

With this, using mylib.old_func anywhere (in mylib itself, its tests, its examples) is an error, while deprecations defined in third-party packages or the stdlib are not reported. Matching would work exactly like deprecated_calls_exclude (prefix on the fully qualified name of the definition), and deprecated_calls_exclude would still apply on top of it, so mylib can be included while mylib.compat is excluded.

Pitch

This is the use case of a library that deprecates its own API:

  1. Its own code must never use its own deprecated symbols. Otherwise it ships code that emits DeprecationWarnings that downstream users can do nothing about. This should fail CI.
  2. Deprecations in its dependencies must NOT fail CI. Libraries are tested against dependency ranges, so a new upstream release that adds a @deprecated breaks every unrelated PR on the next CI run. Often the fix is not even possible yet: when the replacement only exists in the release that deprecated the old name, switching to it means raising the lower bound of the dependency, which is a decision of its own.

At runtime we already get exactly this split with a pytest filterwarnings = ["error:.*mylib\..* is deprecated:DeprecationWarning"] entry. Statically, none of the current options can express it:

  • report_deprecated_as_note is global: it makes our own deprecations non-fatal too.
  • Per-module overrides select the module being checked, not the module that defines the deprecated symbol, so they can't tell the two cases apart.
  • deprecated_calls_exclude can, but only by listing every dependency. That list has to include transitive packages reached through inheritance (a deprecated method inherited from a base class in another package is reported with that package's fully qualified name), test-only dependencies, and one entry per distribution when several share a namespace package (in our case a family of libraries sharing the frequenz.* namespace, so excluding frequenz would hide our own deprecations as well). The list has to be updated whenever dependencies change, and forgetting an entry brings back the exact CI break it is meant to prevent. An include list, on the other hand, is a single entry that never changes.

deprecated_calls_exclude was added (#18641) for the opposite use case: a library excluding its own package because its tests exercise its deprecated API (#18435, and this comment in #18192). Both cases seem common for libraries, and only this one has no reasonable configuration today.

The change should be small: it touches the two places that check deprecated_calls_exclude, TypeChecker.warn_deprecated and TypeAnalyser.check_and_warn_deprecated, plus the option, docs and tests. I'd be happy to send a PR if this is acceptable.

A possible generalization is a per-package severity. An include list makes dependency deprecations silent, but what would fit even better is keeping them visible but non-fatal, for example with report_deprecated_as_note = true plus a way to report mylib as an error anyway. That is close to what @JelleZijlstra suggested in #18435 (comment) (a pattern mapped to a behavior, such as ignore, note or error); deprecated_calls_include would then be the special case "everything else is ignored". I know notes without errors were questioned in #18192, so I'm not attached to this part and it could be split into a separate issue; the include list alone already solves the CI problem.

Having something like this might also make it easier to enable deprecated by default, as announced in the 1.14 release notes, since projects could keep deprecations they don't control from failing their checks.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions