tantale / deprecated

Python @deprecated decorator to deprecate old python classes, functions or methods.
MIT License
301 stars 32 forks source link

Library does not respect warning filters #15

Closed anuppari closed 4 years ago

anuppari commented 4 years ago

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

tantale commented 4 years ago

One possibility is to use the action kwarg:

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

Does it fix your problem?

anuppari commented 4 years ago

That was a minimum working example. In actuality, we use the deprecated decorator in a library, and want to allow clients to decide which warnings to suppress. Setting the action to "ignore" seems to negate the whole point of having the decorator in the first place.

anuppari commented 4 years ago

The documentation seems to suggest that the python warning control system is used, which should allow users to control warnings. Even the command line warning suppression doesn't work (python -W ignore script.py), let alone the warning control filters.

tantale commented 4 years ago

The current @deprecated behavior is to call warnings.simplefilter(action) (where action is "always" by default, see The Warnings Filter) each time the function is call. That way, this ensure the warning message is shown: when warnings.warn() is called, people expect to see a warning message. And they are happy with that.

The problem with warnings.simplefilter(...) is that it update or reset a global list of filters. filters is a module-level variable defined as follow (Python 3.7 implementation):

filters = [
    (
        'default',
        None,
        DeprecationWarning,
        '__main__',
        0,
    ),
    (
        'ignore',
        None,
        '<value is a self-reference, replaced by this string>',
        None,
        0,
    ),
    (
        'ignore',
        None,
        PendingDeprecationWarning,
        None,
        0,
    ),
    (
        'ignore',
        None,
        ImportWarning,
        None,
        0,
    ),
    (
        'ignore',
        None,
        ResourceWarning,
        None,
        0,
    ),
]

So, the current implementation modify this global variable on every call: this is bad.

A correct implementation of the @deprecated operator could be:

I'll see if this implementation is feasible…