Tutorial

In this tutorial, we will use the Deprecated Library to mark pieces of codes as deprecated. We will also see what’s happened when a user tries to call deprecated codes.

Deprecated function

First, we have this little library composed of a single module: liberty.py:

# coding: utf-8
""" Liberty library is free """

import pprint


def print_value(value):
    """
    Print the value

    :param value: The value to print
    """
    pprint.pprint(value)

You decided to write a more powerful function called better_print() which will become a replacement of print_value(). And you decided that the later function is deprecated.

To mark the print_value() as deprecated, you can use the deprecated() decorator:

# coding: utf-8
""" Liberty library is free """

import pprint

from deprecated import deprecated


@deprecated
def print_value(value):
    """
    Print the value

    :param value: The value to print
    """
    pprint.pprint(value)


def better_print(value, printer=None):
    """
    Print the value using a *printer*.

    :param value: The value to print
    :param printer: Callable used to print the value, by default: :func:`pprint.pprint`
    """
    printer = printer or pprint.pprint
    printer(value)

If the user tries to use the deprecated functions, he will have a warning for each call:

# coding: utf-8
import liberty

liberty.print_value("hello")
liberty.print_value("hello again")
liberty.better_print("Hi Tom!")
$ python use_liberty.py

using_liberty.py:4: DeprecationWarning: Call to deprecated function (or staticmethod) print_value.
  liberty.print_value("hello")
'hello'
using_liberty.py:5: DeprecationWarning: Call to deprecated function (or staticmethod) print_value.
  liberty.print_value("hello again")
'hello again'
'Hi Tom!'

As you can see, the deprecation warning is displayed like a stack trace. You have the source code path, the line number and the called function. This is very useful for debugging. But, this doesn’t help the developer to choose a alternative: which function could he use instead?

To help the developer, you can add a “reason” message. For instance:

# coding: utf-8
""" Liberty library is free """

import pprint

from deprecated import deprecated


@deprecated("This function is rotten, use 'better_print' instead")
def print_value(value):
    """
    Print the value

    :param value: The value to print
    """
    pprint.pprint(value)


def better_print(value, printer=None):
    """
    Print the value using a *printer*.

    :param value: The value to print
    :param printer: Callable used to print the value, by default: :func:`pprint.pprint`
    """
    printer = printer or pprint.pprint
    printer(value)

When the user calls the deprecated functions, he will have a more useful message:

$ python use_liberty.py

using_liberty.py:4: DeprecationWarning: Call to deprecated function (or staticmethod) print_value. (This function is rotten, use 'better_print' instead)
  liberty.print_value("hello")
'hello'
using_liberty.py:5: DeprecationWarning: Call to deprecated function (or staticmethod) print_value. (This function is rotten, use 'better_print' instead)
  liberty.print_value("hello again")
'hello again'
'Hi Tom!'

Deprecated method

Decorating a deprecated method works like decorating a function.

# coding: utf-8
""" Liberty library is free """

import pprint

from deprecated import deprecated


class Liberty(object):
    def __init__(self, value):
        self.value = value

    @deprecated("This method is rotten, use 'better_print' instead")
    def print_value(self):
        """ Print the value """
        pprint.pprint(self.value)

    def better_print(self, printer=None):
        """
        Print the value using a *printer*.

        :param printer: Callable used to print the value, by default: :func:`pprint.pprint`
        """
        printer = printer or pprint.pprint
        printer(self.value)

When the user calls the deprecated methods, like this:

# coding: utf-8
import liberty

obj = liberty.Liberty("Greeting")
obj.print_value()
obj.print_value()
obj.better_print()

He will have:

$ python use_liberty.py

using_liberty.py:5: DeprecationWarning: Call to deprecated method print_value. (This method is rotten, use 'better_print' instead)
  obj.print_value()
'Greeting'
using_liberty.py:6: DeprecationWarning: Call to deprecated method print_value. (This method is rotten, use 'better_print' instead)
  obj.print_value()
'Greeting'
'Greeting'

Note

The call is done to the same object, so we have 3 times ‘Greeting’.

Deprecated class

You can also decide that a class is deprecated.

For instance:

# coding: utf-8
""" Liberty library is free """

import pprint

from deprecated import deprecated


@deprecated("This class is not perfect")
class Liberty(object):
    def __init__(self, value):
        self.value = value

    def print_value(self):
        """ Print the value """
        pprint.pprint(self.value)

When the user use the deprecated class like this:

# coding: utf-8
import liberty

obj = liberty.Liberty("Salutation")
obj.print_value()
obj.print_value()

He will have a warning at object instantiation. Once the object is initialised, no more warning are emitted.

$ python use_liberty.py

using_liberty.py:4: DeprecationWarning: Call to deprecated class Liberty. (This class is not perfect)
  obj = liberty.Liberty("Salutation")
'Salutation'
'Salutation'

If a deprecated class is used, then a warning message is emitted during class instantiation. In other word, deprecating a class is the same as deprecating it’s __new__ class method.

As a reminder, the magic method __new__ will be called when instance is being created. Using this method you can customize the instance creation. the deprecated() decorator patches the __new__ method in order to emmit the warning message before instance creation.

Controlling warnings

Warnings are emitted using the Python warning control. By default, Python installs several warning filters, which can be overridden by the -W command-line option, the PYTHONWARNINGS environment variable and calls to warnings.filterwarnings(). The warnings filter controls whether warnings are ignored, displayed, or turned into errors (raising an exception).

For instance:

import warnings
from deprecated import deprecated


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


if __name__ == '__main__':
    warnings.simplefilter("ignore", category=DeprecationWarning)
    fun()

When the user runs this script, the deprecation warnings are ignored in the main program, so no warning message are emitted:

$ python filter_warnings_demo.py

fun

Deprecation warning classes

The deprecated.classic.deprecated() and deprecated.sphinx.deprecated() functions are using the DeprecationWarning category but you can customize them by using your own category (or hierarchy of categories).

  • category classes which you can use (among other) are:

    Class Description
    DeprecationWarning Base category for warnings about deprecated features when those warnings are intended for other Python developers (ignored by default, unless triggered by code in __main__).
    FutureWarning Base category for warnings about deprecated features when those warnings are intended for end users of applications that are written in Python.
    PendingDeprecationWarning Base category for warnings about features that will be deprecated in the future (ignored by default).

You can define your own deprecation warning hierarchy based on the standard deprecation classes.

For instance:

import warnings

from deprecated import deprecated


class MyDeprecationWarning(DeprecationWarning):
    """ My DeprecationWarning """


class DeprecatedIn26(MyDeprecationWarning):
    """ deprecated in 2.6 """


class DeprecatedIn30(MyDeprecationWarning):
    """ deprecated in 3.0 """


@deprecated(category=DeprecatedIn26, reason="deprecated function")
def foo():
    print("foo")


@deprecated(category=DeprecatedIn30, reason="deprecated function")
def bar():
    print("bar")


if __name__ == '__main__':
    warnings.filterwarnings("ignore", category=DeprecatedIn30)
    foo()
    bar()

When the user runs this script, the deprecation warnings for the 3.0 version are ignored:

$ python warning_classes_demo.py

 foo
 bar
 warning_classes_demo.py:30: DeprecatedIn26: Call to deprecated function (or staticmethod) foo. (deprecated function)
   foo()

Filtering warnings locally

The deprecated.classic.deprecated() and deprecated.sphinx.deprecated() functions can change the warning filtering locally (at function calls).

  • action is one of the following strings:

    Value Disposition
    "default" print the first occurrence of matching warnings for each location (module + line number) where the warning is issued
    "error" turn matching warnings into exceptions
    "ignore" never print matching warnings
    "always" always print matching warnings
    "module" print the first occurrence of matching warnings for each module where the warning is issued (regardless of line number)
    "once" print only the first occurrence of matching warnings, regardless of location

You can define the action keyword parameter to override the filtering warnings locally.

For instance:

import warnings
from deprecated import deprecated


@deprecated(reason="do not call it", action="error")
def foo():
    print("foo")


if __name__ == '__main__':
    warnings.simplefilter("ignore")
    foo()

In this example, even if the global filter is set to “ignore”, a call to the foo() function will raise an exception because the action is set to “error”.

$ python filter_action_demo.py

Traceback (most recent call last):
  File "filter_action_demo.py", line 12, in <module>
    foo()
  File "path/to/deprecated/classic.py", line 274, in wrapper_function
    warnings.warn(msg, category=category, stacklevel=_stacklevel)
DeprecationWarning: Call to deprecated function (or staticmethod) foo. (do not call it)