warnings.warn - some DeprecationWarning gotchas

DeprecationWarnings

It's a good practice to gradually deprecate one's library's API, so that users get advance warning of coming changes. The built in way to do so is Python's warnings module

import warnings

if __name__ == '__main__':
    warnings.warn('deprecation', DeprecationWarning)

By default, they are not reported!

However, if I run this code, there is no output.

$ python3.5 demo.py
$ echo $?
0

The documentation on default warning filters explains:

By default, Python installs several warning filters, which can be overridden by the command-line options passed to -W and calls to filterwarnings().

  • DeprecationWarning and PendingDeprecationWarning, and ImportWarning are ignored.
  • BytesWarning is ignored unless the -b option is given once or twice; in this case this warning is either printed (-b) or turned into an exception (-bb).
  • ResourceWarning is ignored unless Python was built in debug mode.

Forcing defaults from the command line makes them reported

However, if we force the default warning behavior from the command line, we get the warnings - even though theoretically we only specified how often the warnings should be reported, not which warnings to be reported!

$ python3.5 -W d demo.py
demo.py:4: DeprecationWarning: deprecation
  warnings.warn('warning', DeprecationWarning)
$ echo $?
0

Before reading the docs, I suspected the operating system maintainers didn't want to 'spam' the end users of their systems with python library warnings while going about their daily tasks, but apparently this is built into python itself.

So how should I deprecate things?

I'm a big fan of executable documentation, but this might be one of those cases where good old fashioned documentation might be more effective, as we can't expect users of our library to run with warnings enabled.

I would still leave in these deprecations for the project's developers as well as for the pedant users of the library.

Checking against deprecations of our dependencies

That's easier, as we own that code. I would run my builds and tests with -W d at the very least, but I would like to try to run with -W error. Except that I don't want to fail the build if one of my dependencies is using deprecated apis, so probably I would just have a custom main.py where I would explicitly set and reset my warning filters. E.g.: updating the above demo code would give me the following:

import warnings
import os

if os.environ.get('TEST', '0') == '1':
    warnings.filterwarnings(module='.*', action='ignore')
    warnings.filterwarnings(module=__name__, action='error')

if __name__ == '__main__':
    warnings.warn('deprecation', DeprecationWarning)

 

$ TEST=1 python3.5 demo.py
Traceback (most recent call last):
  File "demo.py", line 8, in <module>
    warnings.warn('deprecation', DeprecationWarning)
DeprecationWarning: deprecation
$ echo $?
1

I yet have to test how feasible it is when our library supports multiple versions of a dependent library, e.g.: Django, but my gut feeling is that it should be doable

Beware of the ordering of warning filters

If in the above example we got the order reversed, then our own DeprecationWarnings would be ignored too!

if os.environ.get('TEST', '0') == '1':
    warnings.filterwarnings(module=__name__, action='error')
    warnings.filterwarnings(module='.*', action='ignore')

What do you think? I would love if you would leave a comment - drop me an email at hello@zsoldosp.eu, tell me on Twitter!

Your email address