Skip to content

refactor our deprecation machinery #15301

Open

Description

@nstarman

Blocked by

What is the problem this feature will solve?

Python 3.12 now includes a deprecated decorator that is very similar to the one we have.
https://peps.python.org/pep-0702/

the decorator is also available in earlier Python versions using the official backport in typing_extensions.

What's especially nice about Python's built-in deprecation decorator is that it is recognized statically, so IDEs can tell users not to use the deprecated function from-the-get-go, not once they try to run their code.

Describe the desired outcome

We make typing _extensions a run-time dependency. This is very safe since it’s made by the python core devs for backporting Python’s typing-related features.

We make our own deprecation tools thin wrappers of the ones in base python.

Additional context

We have some additional args, e.g. “since”. typing.deprecated takes the whole message string. We can provide a function deprecation_message that constructs this message identically to how we do it now.

Activity

  1. pllim commented on Sep 9, 2023

    @pllim
    Member

    First of all: Does typing_extensions provide nightly wheels? As a required dependency, we need to be able to test against its dev version effectively. Also, is it diligent in providing wheels for unreleased Python in advance?

    Secondly: This needs to be discussed more widely over at astropy-dev. This machinery is widely used in the ecosystem. We don't want to break downstream unnecessarily, especially if the API mapping isn't 1-to-1.

  2. nstarman commented on Sep 9, 2023

    @nstarman
    MemberAuthor

    First of all: Does typing_extensions provide nightly wheels? As a required dependency, we need to be able to test against its dev version effectively. Also, is it diligent in providing wheels for unreleased Python in advance?

    I looked at their release history and they do have release candidates. The package evolves with Python PEPs so it's not a nightly cadence. Also since it's driven by PEPs there's never anything too surprising, I think mostly bugs (they have bug fix patches).
    It's a pure python package, and yes can be installed and is tested for upcoming python versions.

    Secondly: This needs to be discussed more widely over at astropy-dev. This machinery is widely used in the ecosystem. We don't want to break downstream unnecessarily, especially if the API mapping isn't 1-to-1.

    My suggestion is to use typing[_extensions].deprecated (depending on the python version) to deprecate our deprecation decorator! No code breaks, only issues a DeprecationWarning with a message explaining the transition.

  3. pllim commented on Sep 9, 2023

    @pllim
    Member

    Deprecating something that is supposed to emit deprecation warning would be too confusing. We might have to batch PR downstream packages.

  4. nstarman commented on Sep 9, 2023

    @nstarman
    MemberAuthor

    Or we move Astropy's deprecated into a module-level __getattr__ in astropy.util.decorators. When a library imports Astropy's deprecated it emits the warning to use typing[_extensions].deprecated instead. This way we aren't emitting a deprecation warning when astropy.utils.decoratos.deprecated is called, but only at startup when it's imported. Much less confusing!

    e.g.

    def __getattr__(name):
        ....
        if name == "deprecated":
            warning.warn("`astropy.utils.decorators.deprecated` is being phased out in favor of Python's builtin `typing.deprecated` (py3.12+) and backport at `typing_extensions.deprecated` (py3.12-). If you are a library maintainer please go to Astropy's transition document. If you are using a library please open an Issue / PR asking / helping the library  to upgrade to `typing.deprecated`.", category=DeprecationWarning)
            def astropy_deprecated():
                ...
            return astropy_deprecated
       ...

    Both our decorator and the new one in python acccept the message argument (we just have the additional stuff like "since", "name", and "alternative"), so we can already make the helper function deprecation_message which take all the extra arguments our function uses to construct the message. The deprecation period of astropy.util.decorators.deprecated can be then
    be a full Astropy version, or until py3.12!

    The transition process is then approximately

    From

    from astropy.utils.decorators import deprecated
    
    @deprecated(
        since=...,
        message=...
        name=...,
        alternative=...,
        pending=...,
        obj_type=...,
        warning_type=AstropyDeprecationWarning,
    )
    ...

    To

    from typing import deprecated
    from astropy.utils.decorators import deprecation_message  # or in a different sub-module
    
    @deprecated(
        deprecation_message(since=..., message=..., name=..., alternative=...),
        category= AstropyDeprecationWarning,  # replaces warning_type and pending (this is optional)
        stack_level=...,  # this is new (and optional)
    )
    ...
  5. mhvk commented on Sep 11, 2023

    @mhvk
    Contributor

    How about just using typing.deprecated under the hood? That ensures there is no API change, yet presumably the IDEs will recognize deprecated functions still since typing.deprecated adds a __deprecated__ attribute. Obviously, this does not preclude deprecating our own decorators, but it separates the concerns.

  6. nstarman commented on Sep 11, 2023

    @nstarman
    MemberAuthor

    We can and should do this before deprecating deprecated!
    If it doesn't work under the hood, then the transition won't be seamless over-the-hood.
    I still think it's good to get rid of our deprecated because typing.deprecated is also recognized statically, while I don't believe ours will be because Astropy is not a statically-typed library and lacks a py.typed.

  7. nstarman commented on Sep 12, 2023

    @nstarman
    MemberAuthor

    Lessons learned in #15307: typing.deprecated isn't a 1-to-1 replacement. Our decorator handles classmethod and staticmethod better than does typing.deprecated. Also our decorator modifies the docs where typing.deprecated does not. So we might not want to deprecate our decorator any time soon, just keep on eye on typing.deprecated to see if it ever reaches feature parity.

  8. changed the title [-]Deprecate our deprecation machinery[/-] [+]refactor our deprecation machinery[/+] on Sep 12, 2023
  9. removed
    API changePRs and issues that change an existing API, possibly requiring a deprecation period
    on Sep 12, 2023
  10. moved this from Todo to In Progress in Astropy Infrastructureon Sep 12, 2023
  11. pllim commented on Sep 12, 2023

    @pllim
    Member

    Good to know. Thanks for checking!

  12. mhvk commented on Sep 12, 2023

    @mhvk
    Contributor

    Can we change our code to set the new __deprecated__ attribute just like typing.deprecated does? I like very much the idea to be warned that one uses a deprecated function/class already in the editor!

  13. pllim commented on Sep 12, 2023

    @pllim
    Member

    @mhvk , that sounds reasonable for v6 and should be a separate issue/PR. Thanks!

  14. nstarman commented on Sep 12, 2023

    @nstarman
    MemberAuthor

    See #15310 (comment).
    I added the attribute and cherry-picked the initial code cleanup and improvement commits.

  15. Rekaniry commented on Oct 30, 2024

    @Rekaniry

    Hello guys, can I try to help on this issue?

  16. nstarman commented on Oct 30, 2024

    @nstarman
    MemberAuthor

    Hi @Rekaniry! This is perhaps a challenging Issue to tackle, but you are welcome to try!
    #15301 (comment) outlines the difficulties.

    My suggestion would be to make 3 functions:

    1. make_depr_msg. This should make a deprecation message equivalent to what is currently done in Astropy.
    2. depr_doc. This should modify the decorated function's docstring in the same way that is currently done in Astropy.
    3. deprecate: This is a rewrite of Astropy's implementation, but just does warnings.deprecate(make_depr_msg(), ...)(depr_doc(func))

    Then statically-understood deprecations would look like

    from warnings import deprecated
    
    @deprecated(make_depr_msg(...))  # static analysis sees the correct function
    @depr_doc(...)  # docs are modified separately
    def function(...): ...

    And the statically not-understood version would be:

    from astropy.utils.decorators import deprecated  # static analysis doesn't know this calls warnings.deprecated.
    
    @deprecated(...)
    def function(...): ...
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions