More deprecation decorators

What?

PEP 702 added the warnings.deprecated decorator. This covers whole functions and classes, but there are other types of deprecation as well. For example at matplotlib we maintain a number of additional decorators such as

  • delete_parameter - prepare to delete a no-longer needed parameter
  • rename_parameter - prepare to change the name of a parameter
  • make_keyword_only - prepare for inserting the kw-only marker * in signatures
  • deprecate_privatize_attribute - to make a formerly public attribute private

I propose to add such functionality to the stdlib, either in warnings or in a separate module like deprecation (name t.b.d.)

Why is this useful?

API changes are sometimes required, either to correct previous design flaws or to adapt for new needs. To prevent breaking user code, such changes need to be announced via deprecations, ideally with a runtime warning when the deprecated feature is used.

While API stability is extremely valuable, structures that live for decades must be able to evolve. Authors should decide on changes solely based on user impact (gain vs cost of change). If they decide for a change, they should have tooling that makes the change easy.

Why is this a good fit for the standard library?

  • Such deprecation helpers are not trivial to get right. Hence, users should not have to write them from scratch, but should be able to rely on a central implentation.
  • There are a number of packages related to deprecation on PyPI. However, there does not seem to be a standard. None of them is widely used, some seem inactive, and they all have limited features, mostly implementing variants of warnings.deprecated but not the above mentioned use cases. Packages with a reasonably large user base are in most need for systematic deprecations, but OTOH these are cautious with adding dependencies. It may be hard to justiy an additional dependency to more easily handle deprecations.
  • The scope of the functions/decorators is quite clear. Once written, I don’t expect that the code will need to be altered much. Thus, maintainance burden is low.
  • Like with PEP 702, static checkers could pick up the above cases as well. It’s much more likely they do that for a standard implementation in the stdlib compared to the same feature in a random PyPI package.
  • While cpython is rightfully extra cautious with deprecations, there may be cases that such functionality could be used within cpython itself.
7 Likes

See the previous discussion thread, starting here PEP 702: Marking deprecations using the type system - #30 by davidism and further down, for discussion about deprecating other things. I proposed adding a Deprecated[type, reason] marker, but that was rejected. You’ll probably need to propose something more concrete to have a discussion about it.

1 Like

The concrete suggestion is to add decorators delete_parameter, rename_parameter, make_keyword_only, that can emit runtime warnings. It’s not trivial for library authors to get the detection right. As far as I understand, only a decorator can issue the runtime warning, not any typing acrobatics. Using a decorator for these would also be in line with the recently introduced warnings.deprecated decorator.

2 Likes

I believe most of these functionalities can be attained by @deprecateding specific overloads, which is explicitly allowed by the PEP. For example, preparing to delete a no-longer needed parameter could be done like this:

@overload
@deprecated('Param `foo` is deprecated')
def func(x: int, y: str, foo: float | None): ...

@overload
def func(x: int, y: str): ...

def func(x: int, y: str, foo: float | None = None):
    # Implementation

Did you not consider overloads, or do you believe that this solution is not satisfactory?

EDIT: Ok, I see you’re concerned about runtime warnings, but deprecating overloads will not get you that.

Yes, I’m interested in runtime warnings.

But more generally, @overload can only influence checking, not behavior. For example, for rename_parameter we need to accept both the new and old name as keyword during the deprecation period. @overload cannot help with that. Manually making this work by adding the new parameter alongside the old one and having the right resolution and warning code in the function is far from trivial or readable.