Skip to content

Switch deprecated internals to warnings.deprecated - #15307

Open
nstarman wants to merge 1 commit into
astropy:mainfrom
nstarman:utils-deprecation
Open

nstarman wants to merge 1 commit into
astropy:mainfrom
nstarman:utils-deprecation

Conversation

@nstarman

@nstarman nstarman commented Sep 11, 2023 •

Copy link
Copy Markdown
Member

fixes #15301.

Reimplements astropy.utils.decorators.deprecated on top of PEP 702's warnings.deprecated (Python 3.13+), falling back to the typing_extensions backport on 3.12. Our decorator keeps its own signature, default warning type, docstring rewriting, and classmethod/staticmethod support; only the machinery underneath changes.

typing_extensions is added as a conditional dependency, python_version < '3.13', in both dependencies and build-system.requires (the build imports astropy). It drops out entirely once we require 3.13.

Behaviour changes

Deprecating a class now follows PEP 702 semantics, which differ from what we did before:

  • The warning is raised when the class is subclassed, and when the class itself is instantiated — but not when an already-defined subclass is instantiated. Previously the wrapped __init__/__new__ warned for every subclass instantiation and never on subclass creation.
  • __deprecated__ is set on the wrapped function as well as the wrapper, as PEP 702 specifies.

Both are covered by docs/changes/utils/15307.api.rst and the Notes section of the decorator's docstring. The only in-tree fallout was TestRunner, which subclasses the deprecated TestRunnerBase: importing astropy.tests.runner now warns, so astropy/__init__.py imports it inside the catch_warnings block that was already there for make_test_runner_in.

TODO

  • changelog
  • edit the docs of astropy.utils.decorators.deprecated to mention warnings.deprecated: what ours does differently and when one should use that one instead.

@nstarman nstarman added this to the v6.0 milestone Sep 11, 2023
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for your contribution to Astropy! 🌌 This checklist is meant to remind the package maintainers who will review this pull request of some common things to look for.

  • Do the proposed changes actually accomplish desired goals?
  • Do the proposed changes follow the Astropy coding guidelines?
  • Are tests added/updated as required? If so, do they follow the Astropy testing guidelines?
  • Are docs added/updated as required? If so, do they follow the Astropy documentation guidelines?
  • Is rebase and/or squash necessary? If so, please provide the author with appropriate instructions. Also see instructions for rebase and squash.
  • Did the CI pass? If no, are the failures related? If you need to run daily and weekly cron jobs as part of the PR, please apply the "Extra CI" label. Codestyle issues can be fixed by the bot.
  • Is a change log needed? If yes, did the change log check pass? If no, add the "no-changelog-entry-needed" label. If this is a manual backport, use the "skip-changelog-checks" label unless special changelog handling is necessary.
  • Is this a big PR that makes a "What's new?" entry worthwhile and if so, is (1) a "what's new" entry included in this PR and (2) the "whatsnew-needed" label applied?
  • Is a milestone set? Milestone must be set but we cannot check for it on Actions; do not let the green checkmark fool you.
  • At the time of adding the milestone, if the milestone set requires a backport to release branch(es), apply the appropriate "backport-X.Y.x" label(s) before merge.

@github-actions

Copy link
Copy Markdown
Contributor

👋 Thank you for your draft pull request! Do you know that you can use [ci skip] or [skip ci] in your commit messages to skip running continuous integration tests until you are ready?

@nstarman

Copy link
Copy Markdown
Member Author

Definitely merits a changelog, given the added dependency, however proximate to core python.

return func.__func__ if isinstance(func, _method_types) else func


def _deprecation_message(alternative, pending):

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

In a followup PR this can be made a public function so people can have an Astropy-style deprecation message but use core python for their deprecations.

Comment thread setup.cfg Resolved
@nstarman

Copy link
Copy Markdown
Member Author

Should probably update our decorator to also attach __deprecated__ to the original object for better consistency.

@mhvk

mhvk commented Sep 25, 2023

Copy link
Copy Markdown
Contributor

Should probably update our decorator to also attach __deprecated__ to the original object for better consistency.

Isn't that what we did? We should indeed mimic what typing.deprecated does!

@nstarman

Copy link
Copy Markdown
Member Author

For classmethod and such we set __deprecated__ on the wrapper, not mutating the original object. Adding it to the wrapper is good. But it should also be on the original.

@neutrinoceros

Copy link
Copy Markdown
Contributor

Headsup: the @deprecated decorator isn't targetting the typing module anymore, and it's now proposed to land in warnings (see python/peps#3442)

@neutrinoceros

Copy link
Copy Markdown
Contributor

It's also worth noting that it's actually not available in Python 3.12, and currently aimed at 3.13

@mhvk

mhvk commented Sep 25, 2023

Copy link
Copy Markdown
Contributor

Must say it makes more sense in the warnings module. That likely also means there is no backport, so I guess we should just take this slow.

Still all in favour of mimicking the decorator's behaviour so that IDEs recognize our deprecated too.

@nstarman

Copy link
Copy Markdown
Member Author

Must say it makes more sense in the warnings module. That likely also means there is no backport, so I guess we should just take this slow.

Agreed. This gives us many years to deprecate any duplicated functionality.

Still all in favour of mimicking the decorator's behaviour so that IDEs recognize our deprecated too.

That would be nice. Pyright is probably the most used and we could potentially open an issue/PR. My thought is that this is all done statically and deprecated is recognized by an AST parser, not as a live code object. Short of just matching on the function name, I'm not sure if a static checker can understand our decorator.

@mhvk

mhvk commented Sep 26, 2023

Copy link
Copy Markdown
Contributor

Checking on a function names seems fine, TBH. What decorator called deprecated would not mean to deprecate the code...

@nstarman nstarman changed the title Switch deprecated internals to typing.deprecated Switch deprecated internals to warnings.deprecated Oct 9, 2023
@nstarman nstarman modified the milestones: v6.0, v7.0 Oct 14, 2023
@github-actions github-actions Bot added the Close? Tell stale bot that this issue/PR is stale label Feb 22, 2024
@nstarman nstarman added keep-open and removed Close? Tell stale bot that this issue/PR is stale labels Feb 22, 2024
@astropy astropy deleted a comment from github-actions Bot Feb 22, 2024
@pllim pllim modified the milestones: v7.0.0, v7.1.0 Oct 10, 2024
@astrofrog astrofrog modified the milestones: v7.1.0, v7.2.0 Apr 28, 2025
@astrofrog astrofrog modified the milestones: v7.2.0, v8.0.0 Nov 3, 2025
@astrofrog astrofrog modified the milestones: v8.0.0, v8.1.0 May 5, 2026
@nstarman

Copy link
Copy Markdown
Member Author

Just waiting for us to drop 3.11

Comment thread astropy/utils/decorators.py Outdated
This decorator is a wrapper around `~typing.deprecated`, which was introduced by
`PEP 702 <https://peps.python.org/pep-0702/>`_ in Python 3.12. In lower versions of
Python, the `~typing_extenstions.deprecated` backport is used instead. There are a number of differences
between this decorator and `~typing.deprecated`, which are described below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
between this decorator and `~typing.deprecated`, which are described below.
between this decorator and `~warnings.deprecated`, which are described below.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

here and below

@pllim

pllim commented Jul 20, 2026

Copy link
Copy Markdown
Member

This has multiple conflicts now and I don't think we can even do it yet. Should we just close this PR?

@nstarman

Copy link
Copy Markdown
Member Author

I'm fine to rebase and fix conflicts when we do drop Python 3.11.

@neutrinoceros

Copy link
Copy Markdown
Contributor

We'll need to drop 3.12 as well if we can't agree to have typing-extensions as a conditional dependency. FTR, I'm still very much in favor of it, especially when the alternative is "let's drop 2 non-EOL versions of Python".

@pllim

pllim commented Jul 21, 2026

Copy link
Copy Markdown
Member

Re: #15307 (comment)

On the other hand, I don't like introducing a dependency and then remove it again. This is not urgent so I don't see why we cannot wait longer.

@neutrinoceros

Copy link
Copy Markdown
Contributor

This is not urgent so I don't see why we cannot wait longer.

actually that would be my argument against most API removals (yes, deprecated ones), and these are going much faster in astropy than in most libraries I know of.
Users are now more likely than ever to rely on type checkers to discover deprecated APIs, and with our community-wise tendency to drop them as soon as possible, one more way to find call sites wouldn't be a luxury.

@mhvk

mhvk commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

I thought typing_extensions was requested for quite different reasons elsewhere -- I think that particular case really makes sense, as it is basically a backport of stuff that is already in later versions of python, blessed by python itself. It is also particularly easy to remove in the future, since it will be linked to the python version.

@nstarman

Copy link
Copy Markdown
Member Author

a backport of stuff that is already in later versions of python, blessed by python itself. It is also particularly easy to [auto-fix typing imports], since it will be linked to the python version.

💯

@astrofrog

Copy link
Copy Markdown
Member

@nstarman Python 3.11 has been dropped, so is it time to revisit this?

@nstarman

Copy link
Copy Markdown
Member Author

Yes! I can get to this soon.

@nstarman
nstarman marked this pull request as ready for review September 14, 2026 02:13
This decorator is a wrapper around `warnings.deprecated`, which was introduced by
`PEP 702 <https://peps.python.org/pep-0702/>`_ in Python 3.13. On Python 3.12 the
``typing_extensions.deprecated`` backport is used instead. There are a number of
differences between this decorator and `warnings.deprecated`:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I feel like this is too disruptive. deprecated and its associated decorators in this module is also widely used downstream. Why do we need to change anything here? You are free to use warnings.deprecated internally here as much as you want without touching this module, yes?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I agree that changing our existing decorator is unnecessarily disruptive as it's public API. Ideally we should just... deprecate it ?

@pllim pllim Sep 14, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I don't even wanna think about it. How do you deprecate the deprecation? Haha. But seriously, yes, but not until we help downstream move away from it first. It is going to be a long game.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

How are we going to deprecate the astropy decorator without a process like this?
We have this decorator because Python didn't (at time of writing).
I think a new core-Python mechanism for communicating deprecations is the invitation we should accept to reduce our non-core-code burden.
With this in place we can go further and deprecate the integrated message mechanism, replacing it with a helper function, so deprecations work like

from astropy.utils.decorators import deprecation_message
@warnings.deprecated(deprecation_message(...))
def func(...

Looking at the differences, I think the primary way this breaks downstream is in test suites, where it should have minimal impact on users.
Also, given that warnings.deprecated is recognized by static type checkers and ours isn't, IMO no one should be using ours. E.g. VSCode shows me a visible warning every time I go to use a warnings.deprecated object, even before running the program.
I think users are best served by being notified about deprecations early, which is quite literally impossible with our decorator. Now that is possible and is in Python, IMO it's a question of how we do this, not if.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

so deprecations work like

ah, maybe I should have looked at the PR again before I commented. I was thinking we would use the stdlib decorator directly within astropy itself, but clearly that's not the approach you are going with.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah, and I'd refactor the internals of deprecated to use it to generate the message.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Setback encountered: @astropy.utils.deprecated sets __deprecated__ and prepends a .. deprecated:: <since> block to the docstring. warnings.deprecated sets __deprecated__ ONLY, so the Sphinx directive is not set.
Now I recall this was one reason I started with this PR, to keep the Sphinx stuff.
I'll work around this and open a PR for discussion.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Well, that went pretty disastrously.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Re: deprecation_message -- Instead of refactoring deprecated, what about you copy out the messaging code into this new function? I suspect you won't need like 95% of the stuff in deprecated to build a message?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

#20407 is the current state of the draft.
deprecation uses the new deprecation_msg. It's a nice simplification inside deprecation_msg, and it adds some better error messages for malformed messages.

This branch has not been deployed

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

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

refactor our deprecation machinery

6 participants