Skip to content

Repository files navigation

formerly

PyPI Version Supported Python Versions Tests Coverage report

formerly renames a class that users inherit from, without breaking them.

Install with:

pip install formerly

Usage

Rename the class, and leave the old name behind as an alias:

from formerly import deprecated_class


class NewName(SomeClass): ...


OldName = deprecated_class("OldName", NewName)

Now:

  • Subclassing OldName warns, pointing at the class statement.
  • Instantiating OldName warns, but instantiating a subclass does not: the subclass author gets one warning where the problem is, not one per object.
  • issubclass() and isinstance() checks against OldName accept subclasses of NewName, so code that has already migrated keeps passing the checks of code that has not.
  • OldName is a real class: it pickles, survives copy.deepcopy(), passes inspect.isclass(), and can be registered with an abc.ABCMeta interface.

Type checkers reject a base class that comes from a variable, so users subclassing the alias need OldName: Any = ... on their side, or a per-statement ignore comment.

Warnings use DeprecationWarning and report the paths of the classes involved. Both are configurable, along with the messages themselves:

OldName = deprecated_class(
    "OldName",
    NewName,
    category=MyLibraryDeprecationWarning,
    new_path="mylibrary.NewName",
    subclass_message="{cls} inherits from {old}, which is going away in 3.0.",
)

By default only the first subclass warns, since one warning is enough to tell users to migrate. Pass warn_once=False to warn on every subclass.

See the deprecated_class() docstring for the full signature.

Alternatives

Renaming a base class asks more of a deprecation helper than renaming a function does. Each row below is a behavior that a class users inherit from needs:

  formerly warnings.deprecated debtcollector Deprecated pyDeprecate
Warns where the subclass is defined
Stays quiet when a subclass is instantiated
Warns when the old name is instantiated
Accepts migrated classes in issubclass() and isinstance()
Is a class: pickle, deepcopy, inspect.isclass(), abc
Reported by type checkers

Only the fourth row is unique to formerly: the old name keeps accepting classes that already inherit from the new one, so a library can rename a base class without breaking the isinstance() checks that its own code, or its users' code, runs against the old name.

If nothing checks types against the old name, warnings.deprecated covers the rest, and adds what formerly cannot: type checkers report the deprecation before the code runs. It needs Python 3.13, or typing_extensions for older versions, which behaves identically.

Measured on Python 3.13 against Deprecated 1.3.1, debtcollector 3.1.0 and pyDeprecate 0.11.0. pyDeprecate wraps the class in a proxy object rather than a class, so subclassing it raises TypeError.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages