Giter Club home page Giter Club logo

deprecated's Introduction

Deprecated Decorator

Python @deprecated decorator to deprecate old python classes, functions or methods.

license GitHub release PyPI GitHub Workflow Status Coveralls branch Read the Docs (version)

Installation

pip install Deprecated

Usage

To use this, decorate your deprecated function with @deprecated decorator:

from deprecated import deprecated


@deprecated
def some_old_function(x, y):
    return x + y

You can also decorate a class or a method:

from deprecated import deprecated


class SomeClass(object):
    @deprecated
    def some_old_method(self, x, y):
        return x + y


@deprecated
class SomeOldClass(object):
    pass

You can give a "reason" message to help the developer to choose another function/class:

from deprecated import deprecated


@deprecated(reason="use another function")
def some_old_function(x, y):
    return x + y

Authors

The authors of this library are: Marcos CARDOSO, and Laurent LAPORTE.

The original code was made in this StackOverflow post by Leandro REGUEIRO, Patrizio BERTONI, and Eric WIESER.

deprecated's People

Contributors

12rambau avatar cclauss avatar colindean avatar coroa avatar dhodovsk avatar heidecjj avatar hugovk avatar jpopelka avatar mariusvniekerk avatar mgorny avatar mhendricks avatar phracek avatar santosh653 avatar soxofaan avatar tantale avatar vrcmarcos avatar yarikoptic avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

deprecated's Issues

Type Annotations?

I was wondering whether you'd be open to lifting the typestubs from typeshed (https://github.com/python/typeshed/tree/main/stubs/Deprecated/deprecated) directly into this repository. That would make the types easier to maintain (since they live with the code) and make it easier for users to consume (since they can get them directly when installing deprecated instead of having to install the separate types-deprecated module.

If you're open to this, I'd be happy to spend some time doing the port!

[packit] Propose update failed for release v1.2.9

Packit failed on creating pull-requests in dist-git:

dist-git branch error
master Failed to download file from URL https://files.pythonhosted.org/packages/source/D/Deprecated/Deprecated-v1.2.9.tar.gz. Reason: 'Not Found'.

You can re-trigger the update by adding /packit propose-update to the issue comment.

KeyError: 'adapter' when subclassing class that's deprecated

Expected Behavior

Subclass a class that is marked as deprecated shouldn't raises an error.

Actual Behavior

Subclassing a class that's deprecated raises a TypeError

>>> from deprecated import deprecated
>>> @deprecated
... class MyClass: pass
...
>>> class MyOtherClass(MyClass): pass
...
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/alex/.virtualenvs/uptime-backend/lib/python3.6/site-packages/wrapt/decorators.py", line 128, in __init__
    adapter = kwargs.pop('adapter')
KeyError: 'adapter'
>>>

Environment

  • Python version: 3.6
  • Deprecated version: 1.2.0

Sorry for the duplicate issue with vrcmarcos/python-deprecated#7 but I saw that this is here that I should have opened the issue as the library was renamed.

[ENH] Stacklevel offset

Over at coroa/pandas-indexing#27, I am about to deprecate a pandas accessor, but since these accessors are classes, which are instantiated by pandas upon access the warning is not emitted since the stacklevel is too low.

MWE

import pandas as pd
from deprecated import deprecated

@pd.api.extensions.register_dataframe_accessor("idx")
@deprecated
class IdxAccessor:
    def __init__(self, pandas_obj):
        self._obj = pandas_obj

df = pd.DataFrame()
df.idx

will only emit with a warnings.simplefilter("always") warnings.simplefilter("default") (edit: changed to include "default" which was pointed out below), since the callstack looks like:

  Cell In[4], line 11
    df.idx
  File ~/.local/conda/envs/aneris2/lib/python3.10/site-packages/pandas/core/accessor.py:182 in __get__
    accessor_obj = self._accessor(obj)
  File ~/.local/conda/envs/aneris2/lib/python3.10/site-packages/deprecated/classic.py:169 in wrapped_cls
    warnings.warn(msg, category=self.category, stacklevel=_class_stacklevel)

so that the last stacklevel=2 setting points to the pandas.core.accessor module instead of the user code.

Proposal

Would you accept a PR to add a stacklevel_offset or stacklevel argument to deprecated which would either be added like:

    warnings.warn(msg, category=self.category, stacklevel=_class_stacklevel + stacklevel_offset)

or could be used to replace the default stacklevel, like:

    warnings.warn(msg, category=self.category, stacklevel=_class_stacklevel if stacklevel is None else stacklevel)

versionadded emit DeprecationWarning during pytest

According to the this example versionadded should not emit the warning, but when I run the test code it does.

Expected Behavior

from deprecated.sphinx import versionadded

class Macros(object):
    
    class Macro(object):
        def __call__(self, *args,**kwargs):
            ... 

    @versionadded(version='0.7.0')
    class HiveDatabase(Macro):
        ...

I expected this will work fine

Actual Behavior

when I run pytest...

tests/context/test_macros.py: 12 tests with warnings
  /Users/user/IdeaProjects/airsflow/airsflow/context/macros.py:58: DeprecationWarning: Call to deprecated class HiveDatabase. -- Deprecated since version 0.7.0.
    return cls(ctx, *args, namespace=namespace, context=context, **kwargs)

Environment

  • Python version: 3.6.11
  • Deprecated version: 1.2.10

Packaging deprecated into Fedora

It looks like this project is not packaged in Fedora.
Do you plan to package it into Fedora?
Do you think it would be possible to add Fedora RPM spec file into this repository?

We have a project https://packit.dev/, which would automate it a bit once here is a configuration file .packit.yaml
What do you think about it?

I would be glad for any kind of response.

[packit] Propose update failed for release v1.2.10

Packit failed on creating pull-requests in dist-git:

dist-git branch error
master Failed to download file from URL https://files.pythonhosted.org/packages/source/D/Deprecated/Deprecated-v1.2.10.tar.gz. Reason: 'Not Found'.

You can re-trigger the update by adding /packit propose-update to the issue comment.

/packit propose-update

docstring that starts on the first line get wrongly indented

Expected Behavior

Whatever the convention I select for pydocstring (D212: start on the first line or D213: start on second line) I should get the same results when building documentation with Sphinx. I found out that if I use D212 (default behaviour for ruff) in combination with deprecated, then the indetation is shifted and the parameters are interpreted as quotes.

I created a small repository that you can launch by executing "nox" from the root. that shows the behavior.

This works as expected:

@versionadded(version="0.0.1", reason="because")
def titi (a, b) -> None:
    """
    A one line short description

    pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum

    Args:
        a: the first parameter
        b: a second parameter

    Returns:
        nothing
    """

    return

but this doesn't:

@versionadded(version="0.0.1", reason="because")
def tutu (a, b) -> None:
    """A one line short description

    pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum pim pam poum

    Args:
        a: the first parameter
        b: a second parameter

    Returns:
        nothing
    """

    return

see this RDT page for the result.

Test regression in 1.2.8 with PyPy3

Expected Behavior

Tests should pass, as they did in 1.2.7.

To run tests, I've added ,pypy3 to PyTest conditions in tox.ini.

Actual Behavior

The following tests fails with PyPy3:

==================================================================== FAILURES =====================================================================
___________________________________________________ test_classic_deprecated_class__warns[None] ____________________________________________________

classic_deprecated_class = <class 'tests.test_deprecated.classic_deprecated_class.<locals>.Foo2'>

    def test_classic_deprecated_class__warns(classic_deprecated_class):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            classic_deprecated_class()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
        assert "deprecated class" in str(warn.message)
>       assert warn.filename == __file__, 'Incorrect warning stackLevel'
E       AssertionError: Incorrect warning stackLevel
E       assert '/tmp/depreca...est/python.py' == '/tmp/depreca...deprecated.py'
E         - /tmp/deprecated-1.2.8/tests/test_deprecated.py
E         + /tmp/deprecated-1.2.8/.tox/pypy3/site-packages/_pytest/python.py

tests/test_deprecated.py:150: AssertionError
_________________________________________ test_classic_deprecated_class__warns[classic_deprecated_class1] _________________________________________

classic_deprecated_class = <class 'tests.test_deprecated.classic_deprecated_class.<locals>.Foo2'>

    def test_classic_deprecated_class__warns(classic_deprecated_class):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            classic_deprecated_class()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
        assert "deprecated class" in str(warn.message)
>       assert warn.filename == __file__, 'Incorrect warning stackLevel'
E       AssertionError: Incorrect warning stackLevel
E       assert '/tmp/depreca...est/python.py' == '/tmp/depreca...deprecated.py'
E         - /tmp/deprecated-1.2.8/tests/test_deprecated.py
E         + /tmp/deprecated-1.2.8/.tox/pypy3/site-packages/_pytest/python.py

tests/test_deprecated.py:150: AssertionError
_________________________________________ test_classic_deprecated_class__warns[classic_deprecated_class2] _________________________________________

classic_deprecated_class = <class 'tests.test_deprecated.classic_deprecated_class.<locals>.Foo2'>

    def test_classic_deprecated_class__warns(classic_deprecated_class):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            classic_deprecated_class()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
        assert "deprecated class" in str(warn.message)
>       assert warn.filename == __file__, 'Incorrect warning stackLevel'
E       AssertionError: Incorrect warning stackLevel
E       assert '/tmp/depreca...est/python.py' == '/tmp/depreca...deprecated.py'
E         - /tmp/deprecated-1.2.8/tests/test_deprecated.py
E         + /tmp/deprecated-1.2.8/.tox/pypy3/site-packages/_pytest/python.py

tests/test_deprecated.py:150: AssertionError
_________________________________________ test_classic_deprecated_class__warns[classic_deprecated_class3] _________________________________________

classic_deprecated_class = <class 'tests.test_deprecated.classic_deprecated_class.<locals>.Foo2'>

    def test_classic_deprecated_class__warns(classic_deprecated_class):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            classic_deprecated_class()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
        assert "deprecated class" in str(warn.message)
>       assert warn.filename == __file__, 'Incorrect warning stackLevel'
E       AssertionError: Incorrect warning stackLevel
E       assert '/tmp/depreca...est/python.py' == '/tmp/depreca...deprecated.py'
E         - /tmp/deprecated-1.2.8/tests/test_deprecated.py
E         + /tmp/deprecated-1.2.8/.tox/pypy3/site-packages/_pytest/python.py

tests/test_deprecated.py:150: AssertionError
_________________________________________ test_classic_deprecated_class__warns[classic_deprecated_class4] _________________________________________

classic_deprecated_class = <class 'tests.test_deprecated.classic_deprecated_class.<locals>.Foo2'>

    def test_classic_deprecated_class__warns(classic_deprecated_class):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            classic_deprecated_class()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
        assert "deprecated class" in str(warn.message)
>       assert warn.filename == __file__, 'Incorrect warning stackLevel'
E       AssertionError: Incorrect warning stackLevel
E       assert '/tmp/depreca...est/python.py' == '/tmp/depreca...deprecated.py'
E         - /tmp/deprecated-1.2.8/tests/test_deprecated.py
E         + /tmp/deprecated-1.2.8/.tox/pypy3/site-packages/_pytest/python.py

tests/test_deprecated.py:150: AssertionError
_________________________________________ test_classic_deprecated_class__warns[classic_deprecated_class5] _________________________________________

classic_deprecated_class = <class 'tests.test_deprecated.classic_deprecated_class.<locals>.Foo2'>

    def test_classic_deprecated_class__warns(classic_deprecated_class):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            classic_deprecated_class()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
        assert "deprecated class" in str(warn.message)
>       assert warn.filename == __file__, 'Incorrect warning stackLevel'
E       AssertionError: Incorrect warning stackLevel
E       assert '/tmp/depreca...est/python.py' == '/tmp/depreca...deprecated.py'
E         - /tmp/deprecated-1.2.8/tests/test_deprecated.py
E         + /tmp/deprecated-1.2.8/.tox/pypy3/site-packages/_pytest/python.py

tests/test_deprecated.py:150: AssertionError
_________________________________________ test_classic_deprecated_class__warns[classic_deprecated_class6] _________________________________________

classic_deprecated_class = <class 'tests.test_deprecated.classic_deprecated_class.<locals>.Foo2'>

    def test_classic_deprecated_class__warns(classic_deprecated_class):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            classic_deprecated_class()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
        assert "deprecated class" in str(warn.message)
>       assert warn.filename == __file__, 'Incorrect warning stackLevel'
E       AssertionError: Incorrect warning stackLevel
E       assert '/tmp/depreca...est/python.py' == '/tmp/depreca...deprecated.py'
E         - /tmp/deprecated-1.2.8/tests/test_deprecated.py
E         + /tmp/deprecated-1.2.8/.tox/pypy3/site-packages/_pytest/python.py

tests/test_deprecated.py:150: AssertionError
================================================================ warnings summary =================================================================
tests/test_deprecated_class.py::test_simple_class_deprecation_with_args
  /tmp/deprecated-1.2.8/.tox/pypy3/site-packages/_pytest/python.py:184: DeprecationWarning: Call to deprecated class MyClass. (kwargs class)
    result = testfunction(**testargs)

-- Docs: https://docs.pytest.org/en/latest/warnings.html

----------- coverage: platform linux, python 3.6.9-final-0 -----------
Name                     Stmts   Miss  Cover   Missing
------------------------------------------------------
deprecated/__init__.py       3      0   100%
deprecated/classic.py       71      3    96%   22, 130, 280
deprecated/sphinx.py        36      2    94%   124, 150
------------------------------------------------------
TOTAL                      110      5    95%

============================================================= short test summary info =============================================================
FAILED tests/test_deprecated.py::test_classic_deprecated_class__warns[None] - AssertionError: Incorrect warning stackLevel
FAILED tests/test_deprecated.py::test_classic_deprecated_class__warns[classic_deprecated_class1] - AssertionError: Incorrect warning stackLevel
FAILED tests/test_deprecated.py::test_classic_deprecated_class__warns[classic_deprecated_class2] - AssertionError: Incorrect warning stackLevel
FAILED tests/test_deprecated.py::test_classic_deprecated_class__warns[classic_deprecated_class3] - AssertionError: Incorrect warning stackLevel
FAILED tests/test_deprecated.py::test_classic_deprecated_class__warns[classic_deprecated_class4] - AssertionError: Incorrect warning stackLevel
FAILED tests/test_deprecated.py::test_classic_deprecated_class__warns[classic_deprecated_class5] - AssertionError: Incorrect warning stackLevel
FAILED tests/test_deprecated.py::test_classic_deprecated_class__warns[classic_deprecated_class6] - AssertionError: Incorrect warning stackLevel
==================================================== 7 failed, 148 passed, 1 warning in 20.18s ====================================================

I don't know if this isn't a problem with pytest itself and/or pypy3. However,

Environment

  • Python version:
    $ pypy3 --version
    Python 3.6.9 (1608da62bfc71e8ac775121dd0b21bb72e61c6ea, Dec 25 2019, 15:23:23)
    [PyPy 7.3.0 with GCC 9.2.0]
    
  • Deprecated version: 1.2.8

The dependent `wrapt` package no longer supports Python 2.7 on Windows

Problem statement

According to

install_requires=['wrapt < 2, >= 1.10'],
the deprecated package depends on the wrapt package (most recent 1.*-compatible version).

Unfortunately, the wrapt package does not follow semantic versioning which means that the installation of the deprecated package might fail if a new 1.* release of the wrapt package comes out. This is what happened recently with the release of version 1.13.0 of wrapt not being able to install on Windows+Python2.7. For details refer to GrahamDumpleton/wrapt#189.
Unfortunately, the developers of wrapt do not plan to adhere to semantic versioning, which means that for deprecated to work reliably for its users, it might be a good idea to adapt the version specification of the wrapt package.

Possible solution

First, pin the version of wrapt to ==1.12.1 and release deprecated as 1.2.14
Then pin the version of wrapt to ==1.13.1 and release deprecated as 2.0.0, stating that the support for Python2.7 on Windows is dropped or drop support for Python 2.7 on all platforms (as it might be difficult to specify the combination with Windows in the setup scripts...)

Alternatives considered

After releasing deprecated v1.2.14 with wrapt==1.12.1, one could consider unpinning wrapt again (wrapt<2), however, given that wrapt does not adhere to semantic versioning, this should be considered dangerous (e.g. they consider dropping support for Python2.7 in 1.14.0).

Reproduction steps

Make sure that you do not have wrapt installed. Install deprecated on Windows+Python2.7

python -m pip install deprecated=1.2.13

Expected Behavior

The installation succeeds.

Actual Behavior

The installation fails with the following error:

Collecting wrapt<2,>=1.10
  Using cached wrapt-1.13.1.tar.gz (48 kB)
Using legacy 'setup.py install' for wrapt, since package 'wheel' is not installed.
Installing collected packages: wrapt
    Running setup.py install for wrapt ... error
    ERROR: Command errored out with exit status 1:
     command: 'C:\Python27\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'c:\\users\\361dda~1\\appdata\\local\\temp\\pip-install-9vpyu6\\wrapt\\setup.py'"'"'; __file__='"'"'c:\\users\\361dda~1\\appdata\\local\\temp\\pip-install-9vpyu6\\wrapt\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'c:\users\361dda~1\appdata\local\temp\pip-record-zbb4wg\install-record.txt' --single-version-externally-managed --compile --install-headers 'C:\Python27\Include\wrapt'
         cwd: c:\users\361dda~1\appdata\local\temp\pip-install-9vpyu6\wrapt\
    Complete output (15 lines):
    C:\Python27\lib\distutils\extension.py:133: UserWarning: Unknown Extension options: 'optional'
      warnings.warn(msg)
    running install
    running build
    running build_py
    creating build
    creating build\lib.win-amd64-2.7
    creating build\lib.win-amd64-2.7\wrapt
    copying src\wrapt\decorators.py -> build\lib.win-amd64-2.7\wrapt
    copying src\wrapt\importer.py -> build\lib.win-amd64-2.7\wrapt
    copying src\wrapt\wrappers.py -> build\lib.win-amd64-2.7\wrapt
    copying src\wrapt\__init__.py -> build\lib.win-amd64-2.7\wrapt
    running build_ext
    building 'wrapt._wrappers' extension
    error: Microsoft Visual C++ 9.0 is required. Get it from http://aka.ms/vcpython27
    ----------------------------------------
ERROR: Command errored out with exit status 1: 'C:\Python27\python.exe' -u -c 'import sys, setuptools, tokenize; sys.argv[0] = '"'"'c:\\users\\361dda~1\\appdata\\local\\temp\\pip-install-9vpyu6\\wrapt\\setup.py'"'"'; __file__='"'"'c:\\users\\361dda~1\\appdata\\local\\temp\\pip-install-9vpyu6\\wrapt\\setup.py'"'"';f=getattr(tokenize, '"'"'open'"'"', open)(__file__);code=f.read().replace('"'"'\r\n'"'"', '"'"'\n'"'"');f.close();exec(compile(code, __file__, '"'"'exec'"'"'))' install --record 'c:\users\361dda~1\appdata\local\temp\pip-record-zbb4wg\install-record.txt' --single-version-externally-managed --compile --install-headers 'C:\Python27\Include\wrapt' Check the logs for full command output.

Environment

  • Python version: 2.7
  • Operating system: Windows 10 (10.0.18362)

Deprecate a function parameter — Contribution Guide

Introduction

This article concerns the addition of one (or more) decorator(s) to deprecate the use of a function parameter. For example:

@deprecated_param(version="0.2.3",
                  reason="you may consider using *styles* instead.",
                  deprecated_args='color background_color')
def paragraph(text, color=None, background_color=None, styles=None):
    styles = styles or {}
    if color:
        styles['color'] = color
    if background_color:
        styles['background-color'] = background_color
    html_styles = " ".join("{k}: {v};".format(k=k, v=v) for k, v in styles.items())
    html_text = xml.sax.saxutils.escape(text)
    return ('<p styles="{html_styles}">{html_text}</p>'
            .format(html_styles=html_styles, html_text=html_text))

Such a decorator could be coded as follows:

import functools


def deprecated_param(version, reason, deprecated_args):
    def decorate(func):
        @functools.wraps(func)
        def call(*args, **kwargs):
            # todo: check deprecated arguments here...
            return func(*args, **kwargs)

        return call

    return decorate

Advantages and disadvantages of the decorator

Using a decorator to deprecate the use of a function parameter is interesting and offers some advantages:

  • The decorator allows you to isolate the code that checks the use of this parameter from the rest of the function processing: Separation of Concerns design pattern.
  • The decorator allows explicit documentation of the code, so the use of comments is unnecessary: “Readability matters”.
  • The decorator could be used to enhance the code documentation (the docstring of the decorated function).

Disadvantages:

  • The implementation of such a decorator is necessarily more complex than a specific solution.
  • The resulting function (after decoration) is necessarily slower than the decorated function because of the introspection and parameters usage check.
    Another important consideration:
  • The resulting function (after decoration) must be of the same nature as the decorated function: function, method, static method, etc.

Generalization:

In addition to the case of deprecating a parameter usage, a warning could also be issued in the following cases:

  • A parameter has been renamed: the new name is more meaningful, more explicit, etc.
  • One parameter is replaced by another of more general use.
  • The parameter type changes, it is better to use another one.
  • A parameter is considered obsolete, it will be deleted in a future version.
  • A new parameter has been added, its use is strongly recommended.
  • Etc.

The use cases mentioned above fall into 3 categories:

  1. Deleted parameter,
  2. Added parameter,
  3. Modified parameter (default value, modified type or use).

The third case (modified parameter) is the most difficult to specify in general terms.

Examples of deprecations

(This section is incomplete)
Before starting a complex implementation, it would be necessary to study how it is done, on one hand in the Standard Library, and on the other hand in the popular libraries of the Open Source world such as: pip, urllib3, boto3, six, requests, setuptools, futures, Flask, Django…
At least two questions must be considered regarding deprecation:

  • How is it implemented in the source code?
  • How is it documented?

Implementation

(This section is incomplete)
A parameter deprecation decorator implementation must be flexible. It is necessary to be as flexible as possible and to use what exists for @deprecated.basic.deprecated and @deprecated.sphinx.deprecated. Especially, the use of Wrapt is a must-have.
It is also necessary to define test cases that correspond to the most common scenarios. It is also likely that some atypical situations are clearly not implemented.
For now, I would like to continue supporting Python 2.7, as it is still used by 25% of users (according to the 2018 State of Developer Ecosystem survey).

isinstance() fails with versionadded and versionchanged

Expected Behavior

Using isinstance() for classes annotated with the deprecated library should work as expected, regardless of the number of annotations.

from deprecated.sphinx import versionadded
from deprecated.sphinx import versionchanged
from deprecated.sphinx import deprecated

@versionadded(version='X.Y', reason='some reason')
class VersionAdded:
    pass

@versionadded(version='X.Y', reason='some reason')
class VersionAddedChild(VersionAdded):
    pass

@versionchanged(version='X.Y', reason='some reason')
class VersionChanged:
    pass

@versionchanged(version='X.Y', reason='some reason')
class VersionChangedChild(VersionChanged):
    pass

@deprecated(version='X.Y', reason='some reason')
class Deprecated_:
    pass

@deprecated(version='Y.Z', reason='some reason')
class DeprecatedChild_(Deprecated_):
    pass

@versionadded(version='X.Y')
@versionchanged(version='X.Y.Z')
class AddedChanged:
    pass

# should all be True
print([isinstance(VersionAddedChild(), VersionAddedChild), isinstance(VersionAddedChild(), VersionAdded)])
print([isinstance(VersionChangedChild(), VersionChangedChild), isinstance(VersionChangedChild(), VersionChanged)])
print([isinstance(DeprecatedChild_(), DeprecatedChild_), isinstance(DeprecatedChild_(), Deprecated_)])
print([isinstance(AddedChanged(), AddedChanged)])

Actual Behavior

Tell us what happens instead.

[False, False]
[False, False]
[True, True]   # seems to have been addressed on  0e944e0
[False]

Environment

  • Python version: 3.7.7
  • Deprecated version: 1.2.12

Using deprecated with C++ classes wrapped via pybind11

We plan to use deprecated with our mixed C++/python codebase, including using it to decorate C++ functions and classes at the python level, so that python users see deprecation warnings (instead of just seeing them at compile time). However, there's an odd interaction between pybind11 and deprecated when marking methods of classes as deprecated.

Pybind11-wrapped classes report the type of their methods as <instanceMethod>, not <function> in python. Thus the deprecated code at classic.py:225 will register False and it will fall into the else: raise TypeError(). One possible solution to this would be for that elif on line 225 to read elif inspect.isfunction(wrapped) or inspect.ismethod(wrapped):. I'm not sure whether this is the best approach though.

@ktlim and @TallJimbo would be able to say more about the details of the pybind11 side of things.

LSST's deprecation guidelines are online here, and the initial sketch of updates to that to support deprecating methods of classes is in this PR, which spawned this Issue.

Link to the GitHub is missing in the documentation.

The link to the GitHub project is missing in the documentation.

The setup.py file contains:

    project_urls={
        "Documentation": "https://deprecated.readthedocs.io/en/latest/",
        "Source": "https://github.com/tantale/deprecated",
        "Bug Tracker": "https://github.com/tantale/deprecated/issues",
    }

The "Source" and "Bug Tracker" links must appears in the documentation.

TypeError in deprecated class because of arguments given to __new__

Expected Behavior

No exception, returns class as expected.

# Paste a minimal example that causes the problem.
@deprecated('kwargs class')
class ClassKwargs(object):
    def __init__(self, a=5):
        super(ClassKwargs, self).__init__()
        self.state = a

    def method(self):
        return self.state
        
ClassKwargs(6)

Actual Behavior

Not complete traceback but most important part:

    ClassKwargs(6)
  File "/home/matt/miniconda3/envs/df/lib/python3.5/site-packages/deprecated/classic.py", line 104, in wrapped_cls
    return old_new1(*args, **kwargs)
TypeError: object() takes no parameters

Environment

  • Python version: tested on 2.7, 3.5, 3.6, 3.7
  • Deprecated version: 1.2.3

`deprecated.sphinx` decorators don't update the docstring

Expected Behavior

In the following example, the deprecated.sphinx.deprecated must update the docstring of the class:

from deprecated.sphinx import deprecated


@deprecated(reason="It is deprecated", version="1.2.3")
class Foo:
    """
    Description of Foo
    """

The expected docstring should be something like:

Description of Foo


.. deprecated:: 1.2.3
   It is deprecated

Actual Behavior

The actual docstring is:

Description of Foo

Environment

  • Python version: 3
  • Deprecated version: 1.2.2

[packit] Propose update failed for release v1.2.8

Packit failed on creating pull-requests in dist-git:

dist-git branch error
master Failed to download file from URL https://files.pythonhosted.org/packages/source/D/Deprecated/Deprecated-v1.2.8.tar.gz. Reason: 'Not Found'.

You can re-trigger the update by adding /packit propose-update to the issue comment.

Tests fail on py3.9

Expected Behavior

Tests should pass ;-).

Actual Behavior

This is most likely due to Python 3.9 fixing @classmethod behavior (see also: GrahamDumpleton/wrapt#160). Now the deprecation is correctly reported as 'class method' rather than 'deprecated function (or staticmethod)'.

GLOB sdist-make: /tmp/deprecated/setup.py
py39 inst-nodeps: /tmp/deprecated/.tox/.tmp/package/1/Deprecated-1.2.11.zip
py39 installed: attrs==19.3.0,coverage==4.5.4,Deprecated @ file:///tmp/deprecated/.tox/.tmp/package/1/Deprecated-1.2.11.zip,more-itertools==8.3.0,packaging==20.4,pluggy==0.13.1,py==1.8.1,pyparsing==2.4.7,pytest==5.4.2,pytest-cov==2.9.0,six==1.15.0,wcwidth==0.1.9,wrapt==1.12.1
py39 run-test-pre: PYTHONHASHSEED='2163554996'
py39 run-test: commands[0] | pytest --cov-report term-missing --cov=deprecated tests/
============================= test session starts ==============================
platform linux -- Python 3.9.0b1, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
cachedir: .tox/py39/.pytest_cache
rootdir: /tmp/deprecated, inifile: setup.cfg
plugins: cov-2.9.0
collected 156 items

tests/test.py ..                                                         [  1%]
tests/test_deprecated.py ............................FFFFFFF......       [ 27%]
tests/test_deprecated_class.py .......                                   [ 32%]
tests/test_deprecated_metaclass.py ....                                  [ 34%]
tests/test_sphinx.py ................................................... [ 67%]
...............................FFFFFFF......                             [ 95%]
tests/test_sphinx_class.py ...                                           [ 97%]
tests/test_sphinx_metaclass.py ....                                      [100%]

=================================== FAILURES ===================================
______________ test_classic_deprecated_class_method__warns[None] _______________

classic_deprecated_class_method = <class 'tests.test_deprecated.classic_deprecated_class_method.<locals>.Foo5'>

    def test_classic_deprecated_class_method__warns(classic_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = classic_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5.'
E        +  where 'Call to deprecated class method foo5.' = str(DeprecationWarning('Call to deprecated class method foo5.'))
E        +    where DeprecationWarning('Call to deprecated class method foo5.') = <warnings.WarningMessage object at 0x7f2804045a00>.message

tests/test_deprecated.py:187: AssertionError
_ test_classic_deprecated_class_method__warns[classic_deprecated_class_method1] _

classic_deprecated_class_method = <class 'tests.test_deprecated.classic_deprecated_class_method.<locals>.Foo5'>

    def test_classic_deprecated_class_method__warns(classic_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = classic_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5.'
E        +  where 'Call to deprecated class method foo5.' = str(DeprecationWarning('Call to deprecated class method foo5.'))
E        +    where DeprecationWarning('Call to deprecated class method foo5.') = <warnings.WarningMessage object at 0x7f2804062b50>.message

tests/test_deprecated.py:187: AssertionError
_ test_classic_deprecated_class_method__warns[classic_deprecated_class_method2] _

classic_deprecated_class_method = <class 'tests.test_deprecated.classic_deprecated_class_method.<locals>.Foo5'>

    def test_classic_deprecated_class_method__warns(classic_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = classic_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5. (Good reason)'
E        +  where 'Call to deprecated class method foo5. (Good reason)' = str(DeprecationWarning('Call to deprecated class method foo5. (Good reason)'))
E        +    where DeprecationWarning('Call to deprecated class method foo5. (Good reason)') = <warnings.WarningMessage object at 0x7f2804071190>.message

tests/test_deprecated.py:187: AssertionError
_ test_classic_deprecated_class_method__warns[classic_deprecated_class_method3] _

classic_deprecated_class_method = <class 'tests.test_deprecated.classic_deprecated_class_method.<locals>.Foo5'>

    def test_classic_deprecated_class_method__warns(classic_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = classic_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5. (Good reason)'
E        +  where 'Call to deprecated class method foo5. (Good reason)' = str(DeprecationWarning('Call to deprecated class method foo5. (Good reason)'))
E        +    where DeprecationWarning('Call to deprecated class method foo5. (Good reason)') = <warnings.WarningMessage object at 0x7f28040625b0>.message

tests/test_deprecated.py:187: AssertionError
_ test_classic_deprecated_class_method__warns[classic_deprecated_class_method4] _

classic_deprecated_class_method = <class 'tests.test_deprecated.classic_deprecated_class_method.<locals>.Foo5'>

    def test_classic_deprecated_class_method__warns(classic_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = classic_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5. -- Deprecated since version 1.2.3.'
E        +  where 'Call to deprecated class method foo5. -- Deprecated since version 1.2.3.' = str(DeprecationWarning('Call to deprecated class method foo5. -- Deprecated since version 1.2.3.'))
E        +    where DeprecationWarning('Call to deprecated class method foo5. -- Deprecated since version 1.2.3.') = <warnings.WarningMessage object at 0x7f2804071a30>.message

tests/test_deprecated.py:187: AssertionError
_ test_classic_deprecated_class_method__warns[classic_deprecated_class_method5] _

classic_deprecated_class_method = <class 'tests.test_deprecated.classic_deprecated_class_method.<locals>.Foo5'>

    def test_classic_deprecated_class_method__warns(classic_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = classic_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5.'
E        +  where 'Call to deprecated class method foo5.' = str(DeprecationWarning('Call to deprecated class method foo5.'))
E        +    where DeprecationWarning('Call to deprecated class method foo5.') = <warnings.WarningMessage object at 0x7f28040452b0>.message

tests/test_deprecated.py:187: AssertionError
_ test_classic_deprecated_class_method__warns[classic_deprecated_class_method6] _

classic_deprecated_class_method = <class 'tests.test_deprecated.classic_deprecated_class_method.<locals>.Foo5'>

    def test_classic_deprecated_class_method__warns(classic_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = classic_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5.'
E        +  where 'Call to deprecated class method foo5.' = str(MyDeprecationWarning('Call to deprecated class method foo5.'))
E        +    where MyDeprecationWarning('Call to deprecated class method foo5.') = <warnings.WarningMessage object at 0x7f280405c2e0>.message

tests/test_deprecated.py:187: AssertionError
_______________ test_sphinx_deprecated_class_method__warns[None] _______________

sphinx_deprecated_class_method = <class 'tests.test_sphinx.sphinx_deprecated_class_method.<locals>.Foo5'>

    def test_sphinx_deprecated_class_method__warns(sphinx_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = sphinx_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5.'
E        +  where 'Call to deprecated class method foo5.' = str(DeprecationWarning('Call to deprecated class method foo5.'))
E        +    where DeprecationWarning('Call to deprecated class method foo5.') = <warnings.WarningMessage object at 0x7f2803fcb310>.message

tests/test_sphinx.py:337: AssertionError
_ test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method1] __

sphinx_deprecated_class_method = <class 'tests.test_sphinx.sphinx_deprecated_class_method.<locals>.Foo5'>

    def test_sphinx_deprecated_class_method__warns(sphinx_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = sphinx_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5.'
E        +  where 'Call to deprecated class method foo5.' = str(DeprecationWarning('Call to deprecated class method foo5.'))
E        +    where DeprecationWarning('Call to deprecated class method foo5.') = <warnings.WarningMessage object at 0x7f2803fe6f10>.message

tests/test_sphinx.py:337: AssertionError
_ test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method2] __

sphinx_deprecated_class_method = <class 'tests.test_sphinx.sphinx_deprecated_class_method.<locals>.Foo5'>

    def test_sphinx_deprecated_class_method__warns(sphinx_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = sphinx_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5. (Good reason)'
E        +  where 'Call to deprecated class method foo5. (Good reason)' = str(DeprecationWarning('Call to deprecated class method foo5. (Good reason)'))
E        +    where DeprecationWarning('Call to deprecated class method foo5. (Good reason)') = <warnings.WarningMessage object at 0x7f2803ff10d0>.message

tests/test_sphinx.py:337: AssertionError
_ test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method3] __

sphinx_deprecated_class_method = <class 'tests.test_sphinx.sphinx_deprecated_class_method.<locals>.Foo5'>

    def test_sphinx_deprecated_class_method__warns(sphinx_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = sphinx_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5. (Good reason)'
E        +  where 'Call to deprecated class method foo5. (Good reason)' = str(DeprecationWarning('Call to deprecated class method foo5. (Good reason)'))
E        +    where DeprecationWarning('Call to deprecated class method foo5. (Good reason)') = <warnings.WarningMessage object at 0x7f2803fe1790>.message

tests/test_sphinx.py:337: AssertionError
_ test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method4] __

sphinx_deprecated_class_method = <class 'tests.test_sphinx.sphinx_deprecated_class_method.<locals>.Foo5'>

    def test_sphinx_deprecated_class_method__warns(sphinx_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = sphinx_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5. -- Deprecated since version 1.2.3.'
E        +  where 'Call to deprecated class method foo5. -- Deprecated since version 1.2.3.' = str(DeprecationWarning('Call to deprecated class method foo5. -- Deprecated since version 1.2.3.'))
E        +    where DeprecationWarning('Call to deprecated class method foo5. -- Deprecated since version 1.2.3.') = <warnings.WarningMessage object at 0x7f28040bdac0>.message

tests/test_sphinx.py:337: AssertionError
_ test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method5] __

sphinx_deprecated_class_method = <class 'tests.test_sphinx.sphinx_deprecated_class_method.<locals>.Foo5'>

    def test_sphinx_deprecated_class_method__warns(sphinx_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = sphinx_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5.'
E        +  where 'Call to deprecated class method foo5.' = str(DeprecationWarning('Call to deprecated class method foo5.'))
E        +    where DeprecationWarning('Call to deprecated class method foo5.') = <warnings.WarningMessage object at 0x7f2803fead60>.message

tests/test_sphinx.py:337: AssertionError
_ test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method6] __

sphinx_deprecated_class_method = <class 'tests.test_sphinx.sphinx_deprecated_class_method.<locals>.Foo5'>

    def test_sphinx_deprecated_class_method__warns(sphinx_deprecated_class_method):
        with warnings.catch_warnings(record=True) as warns:
            warnings.simplefilter("always")
            cls = sphinx_deprecated_class_method()
            cls.foo5()
        assert len(warns) == 1
        warn = warns[0]
        assert issubclass(warn.category, DeprecationWarning)
>       assert "deprecated function (or staticmethod)" in str(warn.message)
E       AssertionError: assert 'deprecated function (or staticmethod)' in 'Call to deprecated class method foo5.'
E        +  where 'Call to deprecated class method foo5.' = str(MyDeprecationWarning('Call to deprecated class method foo5.'))
E        +    where MyDeprecationWarning('Call to deprecated class method foo5.') = <warnings.WarningMessage object at 0x7f280405cac0>.message

tests/test_sphinx.py:337: AssertionError
=============================== warnings summary ===============================
tests/test_deprecated_class.py::test_simple_class_deprecation_with_args
  /tmp/deprecated/tests/test_deprecated_class.py:148: DeprecationWarning: Call to deprecated class MyClass. (kwargs class)
    MyClass(5)

-- Docs: https://docs.pytest.org/en/latest/warnings.html

----------- coverage: platform linux, python 3.9.0-beta-1 ------------
Name                     Stmts   Miss  Cover   Missing
------------------------------------------------------
deprecated/__init__.py       3      0   100%
deprecated/classic.py       79      6    92%   25-30, 290
deprecated/sphinx.py        38      2    95%   124, 150
------------------------------------------------------
TOTAL                      120      8    93%

=========================== short test summary info ============================
FAILED tests/test_deprecated.py::test_classic_deprecated_class_method__warns[None]
FAILED tests/test_deprecated.py::test_classic_deprecated_class_method__warns[classic_deprecated_class_method1]
FAILED tests/test_deprecated.py::test_classic_deprecated_class_method__warns[classic_deprecated_class_method2]
FAILED tests/test_deprecated.py::test_classic_deprecated_class_method__warns[classic_deprecated_class_method3]
FAILED tests/test_deprecated.py::test_classic_deprecated_class_method__warns[classic_deprecated_class_method4]
FAILED tests/test_deprecated.py::test_classic_deprecated_class_method__warns[classic_deprecated_class_method5]
FAILED tests/test_deprecated.py::test_classic_deprecated_class_method__warns[classic_deprecated_class_method6]
FAILED tests/test_sphinx.py::test_sphinx_deprecated_class_method__warns[None]
FAILED tests/test_sphinx.py::test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method1]
FAILED tests/test_sphinx.py::test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method2]
FAILED tests/test_sphinx.py::test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method3]
FAILED tests/test_sphinx.py::test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method4]
FAILED tests/test_sphinx.py::test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method5]
FAILED tests/test_sphinx.py::test_sphinx_deprecated_class_method__warns[sphinx_deprecated_class_method6]
================== 14 failed, 142 passed, 1 warning in 0.52s ===================
ERROR: InvocationError for command /tmp/deprecated/.tox/py39/bin/pytest --cov-report term-missing --cov=deprecated tests/ (exited with code 1)
___________________________________ summary ____________________________________
ERROR:   py39: commands failed

Environment

  • Python version: 3.9.0b1
  • Deprecated version: git master (cdde25a)

Deprecate @property

Expected Behavior

Tell us what should happen.

    @deprecated(reason="Use .guid.provider")
    @property
    def provider(self):
        return self.guid.provider

Actual Behavior

Tell us what happens instead.

  File "/Users/glen/scm/plex/PlexTraktSync/plex_trakt_sync/plex_api.py", line 126, in PlexLibraryItem
    def provider(self):
  File "/Users/glen/scm/plex/PlexTraktSync/.direnv/python-3.9.5/lib/python3.9/site-packages/deprecated/classic.py", line 261, in deprecated
    raise TypeError(repr(type(args[0])))
TypeError: <class 'property'>

Environment

  • Python version: 3.9.5
  • Deprecated version: 1.2.12

[packit] Propose downstream failed for release v1.2.11

Packit failed on creating pull-requests in dist-git:

dist-git branch error
f34 Ref 'remotes/origin/f34' did not resolve to an object

You can retrigger the update by adding a comment (/packit propose-downstream) into this issue.

decorated methods with "deprecated" cannot be pickled

Hi! First of all, thank you so much for writing this library, it really helps me maintaining deprecations around my source code.

Nevertheless, I realised that there is one problem related with the serialization process (with both pickle and dill) which I believe is related with the wrapt library and the GrahamDumpleton/wrapt#102 issue, in which the author aims to implement the __reduce_ex__ .

Expected Behavior

Tell us what should happen.

from pickle import dumps

from deprecated import deprecated


@deprecated
def foo():
    return 'bar'


print(dumps(foo))

Actual Behavior

Tell us what happens instead.

Traceback (most recent call last):
  File "~/development/open-source/deprecated/proof.py", line 11, in <module>
    print(dumps(foo))
NotImplementedError: object proxy must define __reduce_ex__()

In case of replacing the pickle module by dill, the error trace is more descriptive:

Traceback (most recent call last):
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/pydevd.py", line 1434, in _exec
    pydev_imports.execfile(file, globals, locals)  # execute the script
  File "/Applications/PyCharm.app/Contents/plugins/python/helpers/pydev/_pydev_imps/_pydev_execfile.py", line 18, in execfile
    exec(compile(contents+"\n", file, 'exec'), glob, loc)
  File "/Users/garciparedes/development/open-source/deprecated/proof.py", line 11, in <module>
    print(dumps(foo))
  File "/Users/garciparedes/development/open-source/deprecated/venv/lib/python3.7/site-packages/dill/_dill.py", line 265, in dumps
    dump(obj, file, protocol, byref, fmode, recurse, **kwds)#, strictio)
  File "/Users/garciparedes/development/open-source/deprecated/venv/lib/python3.7/site-packages/dill/_dill.py", line 259, in dump
    Pickler(file, protocol, **_kwds).dump(obj)
  File "~/development/open-source/deprecated/venv/lib/python3.7/site-packages/dill/_dill.py", line 445, in dump
    StockPickler.dump(self, obj)
  File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 437, in dump
    self.save(obj)
  File "/usr/local/Cellar/python/3.7.6_1/Frameworks/Python.framework/Versions/3.7/lib/python3.7/pickle.py", line 524, in save
    rv = reduce(self.proto)
NotImplementedError: object proxy must define __reduce_ex__()

Environment

  • Python version: 3.7.6
  • Deprecated version: 1.2.8

[feature request] Deprecate names imported with `from b import *`

Expected Behavior

Some old code imported everything from a submodule into the top-level namespace:

# a/__init__.py:
from . import b
from .b import *

We want calling code to use a.b.foo() instead of a.foo(), but do it gently.

Actual Behavior

Basically, we want direct use of a.foo() to be deprecated, but without having list manually list all of the imported names. I currently have this implemented with importlib:

################################################################
# import from b into namespace but mark as deprecated
################################################################
import importlib as __importlib
import deprecated as __deprecated
import types as __types
__b_module = __importlib.import_module('.b', __name__)

# is there an __all__?  if so respect it
if "__all__" in __b_module.__dict__:
    __names = __b_module.__dict__["__all__"]
else:
    # otherwise we import all names that don't begin with _
    __names = [x for x in __b_module.__dict__ if not x.startswith("_")]

# decorate the items as they get pulled into the namespace
globals().update({
    k: (
        __deprecated.deprecated(getattr(__b_module, k), action="always", reason="use `b` namespace")
        if not isinstance(getattr(__b_module, k), __types.ModuleType)
        else getattr(__b_module, k)
    )
    for k in __names
    })

It would be nice to have a way to wrap this into a function or decorator or something similar, so that I could simply do something like:

deprecated.import_all('.b', action="always", reason="use `b` namespace")

I'm not sure how this works with the magic of globals() though.

Environment

  • Python version: 3.8.10
  • Deprecated version: 1.2.10

is there plans to release a 1.2.14 patch version ?

My PR (#61) was recently pushed to the repository, it's nothing fancy but it's solving an issue for anyone using the ruff linter with the docstring default parameter. As the ruff project is getting much attention maybe this could be integrated in a patch release to be available for pypi installation.

Deprecated names?

(Feature request)

I would like to see a way to move a class, function, or method from one name to another, deprecating the old name.

So you could say:

class NewName:
    ...

OldName = deprecated_name(NewName)

I would say that it should raise a warning on direct use (ideally when the code referring to it is compiled, but implementing that would be unacceptably magical).

  • Functions, methods: When called
  • Classes: When subclassed or directly instantiated (maybe when a class-level attribute is accessed as well?)

1.2.13: sphinx warnings `reference target not found`

First of all looks like it is not possible cleanly build documentation usinf raw sphinx-build command.

+ /usr/bin/sphinx-build -n -T -b man docs/source build/sphinx/man
Running Sphinx v4.5.0
making output directory... done
loading intersphinx inventory from https://docs.python.org/3/objects.inv...
loading intersphinx inventory from https://wrapt.readthedocs.io/en/latest/objects.inv...
loading intersphinx inventory from http://flask.pocoo.org/docs/1.0/objects.inv...
loading intersphinx inventory from https://docs.djangoproject.com/en/2.1/_objects/...
intersphinx inventory has moved: http://flask.pocoo.org/docs/1.0/objects.inv -> https://flask.palletsprojects.com/en/1.0.x/objects.inv
building [mo]: targets for 0 po files that are out of date
building [man]: all manpages
updating environment: [new config] 10 added, 0 changed, 0 removed
reading sources... [100%] white_paper
WARNING: autodoc: failed to import module 'deprecated'; the following exception was raised:
No module named 'deprecated'
WARNING: autodoc: failed to import module 'classic' from module 'deprecated'; the following exception was raised:
No module named 'deprecated'
WARNING: autodoc: failed to import module 'sphinx' from module 'deprecated'; the following exception was raised:
No module named 'deprecated'
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
writing... python-deprecated.3 { installation introduction tutorial sphinx_deco white_paper api changelog license contributing } /home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/tutorial.rst:20: WARNING: py:meth reference target not found: deprecated
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/tutorial.rst:120: WARNING: py:func reference target not found: deprecated.deprecated
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/tutorial.rst:155: WARNING: py:func reference target not found: deprecated.classic.deprecated
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/tutorial.rst:155: WARNING: py:func reference target not found: deprecated.sphinx.deprecated
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/tutorial.rst:200: WARNING: py:func reference target not found: deprecated.classic.deprecated
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/tutorial.rst:200: WARNING: py:func reference target not found: deprecated.sphinx.deprecated
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/sphinx_deco.rst:77: WARNING: py:func reference target not found: deprecated.sphinx.deprecated
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/sphinx_deco.rst:78: WARNING: py:func reference target not found: deprecated.sphinx.versionadded
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/sphinx_deco.rst:79: WARNING: py:func reference target not found: deprecated.sphinx.versionchanged
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:78: WARNING: py:mod reference target not found: asyncio.tasks
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:128: WARNING: py:mod reference target not found: flask.app
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:128: WARNING: py:mod reference target not found: flask.helpers
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:168: WARNING: py:exc reference target not found: flask.exthook.ExtDeprecationWarning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:202: WARNING: py:mod reference target not found: django.utils.deprecation
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:204: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInDjango31Warning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:206: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInDjango40Warning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:208: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInNextVersionWarning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:208: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInDjango40Warning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:211: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInNextVersionWarning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:215: WARNING: py:class reference target not found: django.contrib.postgres.forms.ranges.FloatRangeField
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:231: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInDjango31Warning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:235: WARNING: py:meth reference target not found: django.conf.LazySettings.FILE_CHARSET
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:235: WARNING: py:class reference target not found: django.conf.LazySettings
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:255: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInDjango40Warning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:267: WARNING: py:class reference target not found: django.utils.deprecation.warn_about_renamed_method
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:267: WARNING: py:class reference target not found: django.utils.deprecation.RenameMethodsBase
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:292: WARNING: py:class reference target not found: lxml.xpath._XPathEvaluatorBase`(:file:`src/lxml/xpath.pxi
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:312: WARNING: py:class reference target not found: lxml.etree._ElementTree
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:343: WARNING: py:func reference target not found: deprecated.sphinx.deprecated
CHANGELOG.rst:29: WARNING: py:func reference target not found: deprecated.sphinx.deprecated
CHANGELOG.rst:61: WARNING: py:mod reference target not found: deprecated.sphinx
CHANGELOG.rst:80: WARNING: py:class reference target not found: deprecated.sphinx.SphinxAdapter
CHANGELOG.rst:242: WARNING: py:class reference target not found: deprecated.classic.ClassicAdapter
CHANGELOG.rst:248: WARNING: py:class reference target not found: deprecated.classic.ClassicAdapter
CHANGELOG.rst:248: WARNING: py:class reference target not found: deprecated.sphinx.SphinxAdapter
CHANGELOG.rst:284: WARNING: py:mod reference target not found: deprecated.sphinx
CHANGELOG.rst:354: WARNING: py:func reference target not found: deprecated.deprecated
CHANGELOG.rst:422: WARNING: py:func reference target not found: deprecated.deprecated
CHANGELOG.rst:425: WARNING: py:mod reference target not found: deprecated
done
build succeeded, 42 warnings.

Fiirst part of warnings can be fixed by patch like below

--- a/docs/source/conf.py~      2022-05-13 08:06:41.000000000 +0000
+++ b/docs/source/conf.py       2022-05-13 08:10:22.286693027 +0000
@@ -17,9 +17,9 @@
 # add these directories to sys.path here. If the directory is relative to the
 # documentation root, use os.path.abspath to make it absolute, like shown here.
 #
-# import os
-# import sys
-# sys.path.insert(0, os.path.abspath('.'))
+import os
+import sys
+sys.path.insert(0, os.path.abspath('../..'))


 # -- General configuration ------------------------------------------------

That patch is doing exacly what is mentioned in comment above modified lines.
Than .. on building my packages I'm using sphinx-build command with -n switch which shows warmings about missing references. These are not critical issues.
Here is the output with warnings:

+ /usr/bin/sphinx-build -n -T -b man docs/source build/sphinx/man
Running Sphinx v4.5.0
making output directory... done
loading intersphinx inventory from https://docs.python.org/3/objects.inv...
loading intersphinx inventory from https://wrapt.readthedocs.io/en/latest/objects.inv...
loading intersphinx inventory from http://flask.pocoo.org/docs/1.0/objects.inv...
loading intersphinx inventory from https://docs.djangoproject.com/en/2.1/_objects/...
intersphinx inventory has moved: http://flask.pocoo.org/docs/1.0/objects.inv -> https://flask.palletsprojects.com/en/1.0.x/objects.inv
building [mo]: targets for 0 po files that are out of date
building [man]: all manpages
updating environment: [new config] 10 added, 0 changed, 0 removed
reading sources... [100%] white_paper
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
writing... python-deprecated.3 { installation introduction tutorial sphinx_deco white_paper api changelog license contributing } /home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/tutorial.rst:120: WARNING: py:func reference target not found: deprecated.deprecated
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:78: WARNING: py:mod reference target not found: asyncio.tasks
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:128: WARNING: py:mod reference target not found: flask.app
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:128: WARNING: py:mod reference target not found: flask.helpers
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:168: WARNING: py:exc reference target not found: flask.exthook.ExtDeprecationWarning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:202: WARNING: py:mod reference target not found: django.utils.deprecation
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:204: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInDjango31Warning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:206: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInDjango40Warning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:208: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInNextVersionWarning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:208: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInDjango40Warning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:211: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInNextVersionWarning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:215: WARNING: py:class reference target not found: django.contrib.postgres.forms.ranges.FloatRangeField
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:231: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInDjango31Warning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:235: WARNING: py:meth reference target not found: django.conf.LazySettings.FILE_CHARSET
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:235: WARNING: py:class reference target not found: django.conf.LazySettings
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:255: WARNING: py:exc reference target not found: django.utils.deprecation.RemovedInDjango40Warning
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:267: WARNING: py:class reference target not found: django.utils.deprecation.warn_about_renamed_method
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:267: WARNING: py:class reference target not found: django.utils.deprecation.RenameMethodsBase
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:292: WARNING: py:class reference target not found: lxml.xpath._XPathEvaluatorBase`(:file:`src/lxml/xpath.pxi
/home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/docs/source/white_paper.rst:312: WARNING: py:class reference target not found: lxml.etree._ElementTree
CHANGELOG.rst:354: WARNING: py:func reference target not found: deprecated.deprecated
CHANGELOG.rst:422: WARNING: py:func reference target not found: deprecated.deprecated
done
build succeeded, 22 warnings.

Tests fail with wrapt<1.10

Current setup.py express the dependency on wrapt as:

 install_requires=[
        'wrapt < 2, >= 1',
    ],

However using wrapt 1.9.0 (since it's the current version available on debian stretch) fails with:

____________________ ERROR collecting tests/test.py _____________________
tests/test.py:4: in <module>
    import deprecated
deprecated/__init__.py:13: in <module>
    from deprecated.classic import deprecated
deprecated/classic.py:18: in <module>
    class ClassicAdapter(wrapt.AdapterFactory):
E   AttributeError: module 'wrapt' has no attribute 'AdapterFactory'

(as well as in other tests).

Using wrapt 1.10+ works as expected.

  • Python version: 3.5
  • Deprecated version: 3dc742c

"default", "module", and "once" actions do not work in Python 3

Expected Behavior

Paste the following into a file named once.py

from deprecated import deprecated
@deprecated(reason='this should happen once', action='once')
def a():
    return

a()
a()
a()

I expect the deprecation warning to print once:

$ python3 once.py
once.py:6: DeprecationWarning: Call to deprecated function (or staticmethod) a. (this should happen once)
  a()

Actual Behavior

Tell us what happens instead.

$ python3 once.py
once.py:6: DeprecationWarning: Call to deprecated function (or staticmethod) a. (this should happen once)
  a()
once.py:7: DeprecationWarning: Call to deprecated function (or staticmethod) a. (this should happen once)
  a()
once.py:8: DeprecationWarning: Call to deprecated function (or staticmethod) a. (this should happen once)
  a()

Environment

  • Python version: Python 3.6.9
  • Deprecated version: 1.2.9

Additional Info

Note: this issue is slightly different from the issue #24 aims to solve. #24 deals with actions specified by global warning filters while this issue deals with actions specified to the @deprecated decorator.

This is not a problem in Python 2. This is only a problem in Python 3 due to the way warnings.catch_warnings() works. Entering and exiting the warnings.catch_warnings() context manager causes Python 3 to clear out its warning registries. The warning registries keep track of which warnings have fired in order to enforce actions like "once", "default", and "module". When deprecated fires its warning inside warnings.catch_warnings(), the warning registries will always be empty, so "once", "default", and "module" end up behaving like "always". I'm not sure there's much we can do to fix this problem.

Désapprouver un paramètre de fonction — Guide de contribution

Désapprouver un paramètre de fonction

Introduction

Cet article concerne l’ajout d’un (ou plusieurs) décorateur(s) permettant de désapprouver l’utilisation d’un paramètre de fonction. Par exemple :

@deprecated_param(version="0.2.3",
                  reason="you may consider using *styles* instead.",
                  deprecated_args='color background_color')
def paragraph(text, color=None, background_color=None, styles=None):
    styles = styles or {}
    if color:
        styles['color'] = color
    if background_color:
        styles['background-color'] = background_color
    html_styles = " ".join("{k}: {v};".format(k=k, v=v) for k, v in styles.items())
    html_text = xml.sax.saxutils.escape(text)
    return ('<p styles="{html_styles}">{html_text}</p>'
            .format(html_styles=html_styles, html_text=html_text))

Un tel décorateur pourrait être codé ainsi :

import functools


def deprecated_param(version, reason, deprecated_args):
    def decorate(func):
        @functools.wraps(func)
        def call(*args, **kwargs):
            # todo: check deprecated arguments here...
            return func(*args, **kwargs)

        return call

    return decorate

Avantages et désavantages du décorateur

Utiliser un décorateur pour désapprouver l’usage d’un paramètre de fonction est intéressant et offre certains avantages :

  • Le décorateur permet d’isoler le code qui vérifie l’usage de ce paramètre du reste du traitement de la fonction : séparation des concepts (Separation of Concerns design pattern).
  • Le décorateur permet une documentation explicite du code, l’utilisation de commentaire est donc inutile : « La lisibilité compte ».
  • Le décorateur pourrait permettre d’enrichir la documentation du code (la docstring de la fonction décorée).

Désavantage :

  • L’implémentation d’un tel décorateur est nécessairement plus complexe qu’une solution spécifique.
  • La fonction résultante de la décoration est nécessairement plus lente que la fonction décorée à cause l’introspection et de la vérification de l’usage des paramètres.

Autre considération importante :

  • La fonction résultante de la décoration doit être de même nature que la fonction décorée : fonction, méthode, méthode statique, etc.

Généralisation

Outre le cas de déprécation de l’usage d’un paramètre, on pourrait aussi émettre un avertissement dans les cas suivants :

  • Un paramètre a été renommé : le nouveau nom est plus parlant, plus explicite, etc.
  • Un paramètre est remplacé par un autre d’usage plus général.
  • Le type d’un paramètre change, il est préférable d’en utiliser un autre.
  • Un paramètre est considéré obsolète, il sera supprimé dans une version future.
  • Un nouveau paramètre a été rajouté, son usage est fortement recommandé.
  • Etc.

Les cas d’utilisation cités ci-dessus rentrent dans 3 catégories :

  1. Paramètre supprimé,
  2. Paramètre ajouté,
  3. Paramètre modifié (valeur par défaut, type ou usage modifié).
    Le troisième cas (paramètre modifié) est le plus difficile à spécifier de manière générale.

Exemples de déprécations

(Cette rubrique est incomplète)
Avant de ce lancer dans une implémentation complexe, il faudrait étudier ce qui ce fait, d’une part dans la Bibliothèque Standard, et d’autre part dans les bibliothèques populaires du monde Open Source telles que : pip, urllib3, boto3, six, requests, setuptools, futures, Flask, Django, etc.
Au moins deux questions sont à considérer concernant la déprécation :

  • Comment est-ce mis en œuvre dans le code source ?
  • Comment est-ce documenté ?

Implémentation

(Cette rubrique est incomplète)
L’implémentation d’un décorateur de déprécation de paramètre doit être flexible. Il est nécessaire d’être aussi souple que possible et de reprendre ce qu’il existe pour @deprecated.basic.deprecated et @deprecated.sphinx.deprecated. Notamment, l’usage de Wrapt est un must-have.
Il faut aussi définir des cas de tests qui correspondent aux scénarios les plus courant. Il est probable aussi que certaines situations atypiques soient clairement non implémentées.
Pour l’instant, je souhaite continuer le support de Python 2.7, car il est encore utilisé part 25 % des utilisateurs (selon le sondage State of Developer Ecosystem de 2018).

Control sphinx width

This issue tracker is a tool to address bugs in Deprecated itself.
Please use Stack Overflow for general questions about using Deprecated
or issues not related to Deprecated Library.

If you'd like to report a bug in Deprecated, fill out the template below. Provide
any extra information that may be useful/related to your problem.
Ideally, create an Minimal, Complete, and Verifiable example,
which helps us understand the problem and helps check that it is not caused by something in your code.


Expected Behavior

I'm hoping to use the decorator to create sphinx output.

print(a.__doc__)
.. deprecated:: 3.0.35
   This staticmethod has been deprecated. :func:`get_it_id()` is
   recommended instead. Example:

   .. code-block:: python
      from test_lib_dry_lab.lib.aplace.anotherlongerplace import Lookup
      rwo = Lookup('ID222').item[0]
      rwo.get_it_id()
      'AY20'

Actual Behavior

When using sphinx to deprecate a function, I'm noticing its continuing text on a new line. Is there a way to prevent this?

.. deprecated:: 3.0.35
   This staticmethod has been deprecated. :func:`get_it_id()` is
   recommended instead. Example:

   .. code-block:: python
      from test_lib_dry_lab.lib.aplace.anotherlongerplace import
   Lookup
      rwo = Lookup('ID222').item[0]
      rwo.get_it_id()
      'AY20'

Environment

  • Python version: 3.7
  • Deprecated version: 1.2.10

1.2.13: pytest warnings

I'm trying to package your module as an rpm package. So I'm using the typical PEP517 based build, install and test cycle used on building packages from non-root account.

  • python3 -sBm build -w --no-isolation
  • because I'm calling build with --no-isolation I'm using during all processes only locally installed modules
  • install .whl file in </install/prefix>
  • run pytest with PYTHONPATH pointing to sitearch and sitelib inside </install/prefix>

Here is pytest output:

+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-deprecated-1.2.13-5.fc35.x86_64/usr/lib64/python3.8/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-deprecated-1.2.13-5.fc35.x86_64/usr/lib/python3.8/site-packages
+ /usr/bin/pytest -ra
=========================================================================== test session starts ============================================================================
platform linux -- Python 3.8.13, pytest-7.1.2, pluggy-1.0.0
rootdir: /home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13, configfile: setup.cfg
collected 159 items

tests/test.py ..                                                                                                                                                     [  1%]
tests/test_deprecated.py .........................................                                                                                                   [ 27%]
tests/test_deprecated_class.py .......                                                                                                                               [ 31%]
tests/test_deprecated_metaclass.py ....                                                                                                                              [ 33%]
tests/test_sphinx.py ..................................................................................                                                              [ 85%]
tests/test_sphinx_adapter.py ............                                                                                                                            [ 93%]
tests/test_sphinx_class.py .......                                                                                                                                   [ 97%]
tests/test_sphinx_metaclass.py ....                                                                                                                                  [100%]

============================================================================= warnings summary =============================================================================
tests/test_deprecated_class.py::test_simple_class_deprecation_with_args
  /home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/tests/test_deprecated_class.py:148: DeprecationWarning: Call to deprecated class MyClass. (kwargs class)
    MyClass(5)

tests/test_sphinx_class.py::test_isinstance_deprecated
  /home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/tests/test_sphinx_class.py:134: DeprecationWarning: Call to deprecated class DeprecatedChildCls. (some reason) -- Deprecated since version Y.Z.
    instance = DeprecatedChildCls()

tests/test_sphinx_class.py::test_isinstance_deprecated
  /home/tkloczko/rpmbuild/BUILD/deprecated-1.2.13/deprecated/classic.py:173: DeprecationWarning: Call to deprecated class DeprecatedCls. (some reason) -- Deprecated since version X.Y.
    return old_new1(cls, *args, **kwargs)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
===================================================================== 159 passed, 3 warnings in 0.44s ======================================================================

`action="once"` not working

Expected Behavior

When setting the action="once" option, the deprecation warning should only be logged once.

from deprecated import deprecated


@deprecated(action='once')
def my_func():
    print("running the old func")


if __name__ == "__main__":
    my_func()
    my_func()
    my_func()

Actual Behavior

The deprecation warning is displayed every time the function is called:

DeprecationWarning: Call to deprecated function (or staticmethod) my_func.
  my_func()
DeprecationWarning: Call to deprecated function (or staticmethod) my_func.
  my_func()
DeprecationWarning: Call to deprecated function (or staticmethod) my_func.
  my_func()
running the old func
running the old func
running the old func

Environment

  • Python version: 3.10.6
  • Deprecated version: 1.2.13

is travis still in use ?

I was looking at the deprecated files and see there is a .travis.yml file. I remember last year that travis dropped support for open source repositories. There is no travis badge in the readme so I was asking myself, is it still used ?

If no would you be opened to move the test to a github action ?

Deprecate arguments

This is a feature idea, not a bug report.

It would be great if deprecated could warn about deprecated arguments, e.g. like

@deprecated_arg(name="old_arg_name", since="1.2", version=mypackage.__version__, reason="'old_arg_name' is not consistent with some reference")
def my_function(old_arg_name, other_args):
    pass

which would yield a message like:

"my_function" called with deprecated argument "old_arg_name"

Optionally with a new name hint, so

@deprecated_arg(name="old_arg_name", new_name="new_arg_name", since="1.2", version=mypackage.__version__)
def my_function(old_arg_name, other_args):
    pass

which would yield a message like:

"my_function" called with deprecated argument "old_arg_name". Please use "new_arg_name"

[fyi] stubs for deprecated were submitted to typeshed

Type stubs for this library were submitted to typeshed in python/typeshed#4423

You don't need to take any action, but:
a) If you wish to include type stubs as part of your package, we're happy to transfer them to this repo (or potentially aid with the addition of inline types)
b) If you want us to remove the stubs from typeshed, we will!

Feature request: strip Sphinx syntax from sphinx.deprecated warning message

Say you use the sphinx decorator with Sphinx syntax references like this:

from deprecated.sphinx import deprecated

@deprecated(reason="Use :py:func:`add` instead", version="1.2.3")
def add(x, y):
    return x + y

add(2,3)

the DeprecationWarning will say:

Call to deprecated function (or staticmethod) add. (Use :py:func:`add` instead) -- Deprecated since version 1.2.3.

For a normal user of my library this :py:func: looks weird.
The user typically does not know/care that I use Sphinx as documentation build tool, let alone that they understand this syntax.

Feature request:

Strip this kind of Sphinx syntax from the DeprecationWarning message

Library does not respect warning filters

Expected Behavior

When warning filters are applied, I'd expect the warnings to be suppressed.

import warnings
from deprecated import deprecated

warnings.simplefilter("ignore")

@deprecated(version='1.2.1', reason="deprecated function")
def fun():
    print("fun")

fun()

Actual Behavior

The script emits a warning message:

test_dep.py:10: DeprecationWarning: Call to deprecated function (or staticmethod) fun. (deprecated function) -- Deprecated since version 1.2.1.
  fun()
fun

Environment

  • Python version: 3.6.9
  • Deprecated version: 1.2.7

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.