Skip to content

Replace warnings.warn with cbook._warn_external or logging.warning #12006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 60 additions & 18 deletions doc/devel/contributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -449,29 +449,71 @@ Then they will receive messages like::
Which logging level to use?
~~~~~~~~~~~~~~~~~~~~~~~~~~~

There are five levels at which you can emit messages. `logging.critical` and
`logging.error` are really only there for errors that will end the use of the
library but not kill the interpreter. `logging.warning` overlaps with the
`warnings` library. The `logging tutorial`_ suggests that the difference
between `logging.warning` and `warnings.warn` is that `warnings.warn` should
be used for things the user must change to stop the warning (typically in the
source), whereas `logging.warning` can be more persistent. Moreover, note
that `warnings.warn` will by default only emit a given warning *once*, whereas
`logging.warning` will display the message every time it is called.
There are five levels at which you can emit messages.

- `logging.critical` and `logging.error` are really only there for errors that
will end the use of the library but not kill the interpreter.
- `logging.warning` and `cbook._warn_external` are used to warn the user,
see below.
- `logging.info` is for information that the user may want to know if the
program behaves oddly. They are not displayed by default. For instance, if
an object isn't drawn because its position is ``NaN``, that can usually
be ignored, but a mystified user could call
``logging.basicConfig(level=logging.INFO)`` and get an error message that
says why.
- `logging.debug` is the least likely to be displayed, and hence can be the
most verbose. "Expected" code paths (e.g., reporting normal intermediate
steps of layouting or rendering) should only log at this level.

By default, `logging` displays all log messages at levels higher than
`logging.WARNING` to `sys.stderr`.

Calls to `logging.info` are not displayed by default. They are for
information that the user may want to know if the program behaves oddly.
For instance, if an object isn't drawn because its position is ``NaN``,
that can usually be ignored, but a mystified user could call
``logging.basicConfig(level=logging.INFO)`` and get an error message that
says why.
The `logging tutorial`_ suggests that the difference
between `logging.warning` and `cbook._warn_external` (which uses
`warnings.warn`) is that `cbook._warn_external` should be used for things the
user must change to stop the warning (typically in the source), whereas
`logging.warning` can be more persistent. Moreover, note that
`cbook._warn_external` will by default only emit a given warning *once* for
each line of user code, whereas `logging.warning` will display the message
every time it is called.

`logging.debug` is the least likely to be displayed, and hence can be the most
verbose. "Expected" code paths (e.g., reporting normal intermediate steps of
layouting or rendering) should only log at this level.
By default, `warnings.warn` displays the line of code that has the `warn` call.
This usually isn't more informative than the warning message itself. Therefore,
Matplotlib uses `cbook._warn_external` which uses `warnings.warn`, but goes
up the stack and displays the first line of code outside of Matplotlib.
For example, for the module::

# in my_matplotlib_module.py
import warnings

def set_range(bottom, top):
if bottom == top:
warnings.warn('Attempting to set identical bottom==top')


running the script::

from matplotlib import my_matplotlib_module
my_matplotlib_module.set_range(0, 0) #set range


will display::

UserWarning: Attempting to set identical bottom==top
warnings.warn('Attempting to set identical bottom==top')

Modifying the module to use `cbook._warn_external`::

from matplotlib import cbook

def set_range(bottom, top):
if bottom == top:
cbook._warn_external('Attempting to set identical bottom==top')

and running the same script will display::

UserWarning: Attempting to set identical bottom==top
my_matplotlib_module.set_range(0, 0) #set range

.. _logging tutorial: https://docs.python.org/3/howto/logging.html#logging-basic-tutorial

Expand Down
75 changes: 36 additions & 39 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,9 +244,8 @@ def _set_logger_verbose_level(level_str='silent', file_str='sys.stdout'):
fileo = open(file_str, 'w')
# if this fails, we will just write to stdout
except IOError:
warnings.warn('could not open log file "{0}"'
'for writing. Check your '
'matplotlibrc'.format(file_str))
_log.warning('could not open log file "{0}" for writing. '
'Check your matplotlibrc'.format(file_str))
console = logging.StreamHandler(fileo)
console.setLevel(newlev)
_log.addHandler(console)
Expand Down Expand Up @@ -307,8 +306,9 @@ def set_level(self, level):
if self._commandLineVerbose is not None:
level = self._commandLineVerbose
if level not in self.levels:
warnings.warn('matplotlib: unrecognized --verbose-* string "%s".'
' Legal values are %s' % (level, self.levels))
cbook._warn_external('matplotlib: unrecognized --verbose-* '
'string "%s". Legal values are %s' %
(level, self.levels))
else:
self.level = level

Expand Down Expand Up @@ -487,9 +487,9 @@ def checkdep_ps_distiller(s):
gs_exec, gs_v = checkdep_ghostscript()
if not gs_exec:
flag = False
warnings.warn('matplotlibrc ps.usedistiller option can not be used '
'unless ghostscript 9.0 or later is installed on your '
'system')
_log.warning('matplotlibrc ps.usedistiller option can not be used '
'unless ghostscript 9.0 or later is installed on your '
'system')

if s == 'xpdf':
pdftops_req = '3.0'
Expand All @@ -502,9 +502,9 @@ def checkdep_ps_distiller(s):
pass
else:
flag = False
warnings.warn(('matplotlibrc ps.usedistiller can not be set to '
'xpdf unless xpdf-%s or later is installed on '
'your system') % pdftops_req)
_log.warning(('matplotlibrc ps.usedistiller can not be set to '
'xpdf unless xpdf-%s or later is installed on '
'your system') % pdftops_req)

if flag:
return s
Expand All @@ -522,22 +522,22 @@ def checkdep_usetex(s):

if shutil.which("tex") is None:
flag = False
warnings.warn('matplotlibrc text.usetex option can not be used unless '
'TeX is installed on your system')
_log.warning('matplotlibrc text.usetex option can not be used unless '
'TeX is installed on your system')

dvipng_v = checkdep_dvipng()
if not compare_versions(dvipng_v, dvipng_req):
flag = False
warnings.warn('matplotlibrc text.usetex can not be used with *Agg '
'backend unless dvipng-%s or later is installed on '
'your system' % dvipng_req)
_log.warning('matplotlibrc text.usetex can not be used with *Agg '
'backend unless dvipng-%s or later is installed on '
'your system' % dvipng_req)

gs_exec, gs_v = checkdep_ghostscript()
if not compare_versions(gs_v, gs_req):
flag = False
warnings.warn('matplotlibrc text.usetex can not be used unless '
'ghostscript-%s or later is installed on your system'
% gs_req)
_log.warning('matplotlibrc text.usetex can not be used unless '
'ghostscript-%s or later is installed on your system'
% gs_req)

return flag

Expand Down Expand Up @@ -961,17 +961,17 @@ def _rc_params_in_file(fname, fail_on_error=False):
tup = strippedline.split(':', 1)
if len(tup) != 2:
error_details = _error_details_fmt % (cnt, line, fname)
warnings.warn('Illegal %s' % error_details)
_log.warning('Illegal %s' % error_details)
continue
key, val = tup
key = key.strip()
val = val.strip()
if key in rc_temp:
warnings.warn('Duplicate key in file "%s", line #%d' %
(fname, cnt))
_log.warning('Duplicate key in file "%s", line #%d' %
(fname, cnt))
rc_temp[key] = (val, line, cnt)
except UnicodeDecodeError:
warnings.warn(
_log.warning(
('Cannot decode configuration file %s with '
'encoding %s, check LANG and LC_* variables')
% (fname, locale.getpreferredencoding(do_setlocale=False) or
Expand All @@ -990,8 +990,8 @@ def _rc_params_in_file(fname, fail_on_error=False):
config[key] = val # try to convert to proper type or skip
except Exception as msg:
error_details = _error_details_fmt % (cnt, line, fname)
warnings.warn('Bad val "%s" on %s\n\t%s' %
(val, error_details, msg))
_log.warning('Bad val "%s" on %s\n\t%s' %
(val, error_details, msg))

for key, (val, line, cnt) in rc_temp.items():
if key in defaultParams:
Expand All @@ -1002,8 +1002,8 @@ def _rc_params_in_file(fname, fail_on_error=False):
config[key] = val # try to convert to proper type or skip
except Exception as msg:
error_details = _error_details_fmt % (cnt, line, fname)
warnings.warn('Bad val "%s" on %s\n\t%s' %
(val, error_details, msg))
_log.warning('Bad val "%s" on %s\n\t%s' %
(val, error_details, msg))
elif key in _deprecated_ignore_map:
version, alt_key = _deprecated_ignore_map[key]
cbook.warn_deprecated(
Expand Down Expand Up @@ -1345,10 +1345,9 @@ def use(arg, warn=True, force=False):
# If we are going to force the switch, never warn, else, if warn
# is True, then direct users to `plt.switch_backend`
if (not force) and warn:
warnings.warn(
cbook._warn_external(
("matplotlib.pyplot as already been imported, "
"this call will have no effect."),
stacklevel=2)
"this call will have no effect."))

# if we are going to force switching the backend, pull in
# `switch_backend` from pyplot. This will only happen if
Expand Down Expand Up @@ -1428,7 +1427,7 @@ def _init_tests():
from matplotlib import ft2font
if (ft2font.__freetype_version__ != LOCAL_FREETYPE_VERSION or
ft2font.__freetype_build_type__ != 'local'):
warnings.warn(
_log.warning(
"Matplotlib is not built with the correct FreeType version to run "
"tests. Set local_freetype=True in setup.cfg and rebuild. "
"Expect many image comparison failures below. "
Expand All @@ -1437,9 +1436,7 @@ def _init_tests():
"Freetype build type is {2}local".format(
LOCAL_FREETYPE_VERSION,
ft2font.__freetype_version__,
"" if ft2font.__freetype_build_type__ == 'local' else "not "
)
)
"" if ft2font.__freetype_build_type__ == 'local' else "not "))

try:
import pytest
Expand Down Expand Up @@ -1769,12 +1766,12 @@ def inner(ax, *args, data=None, **kwargs):
elif label_namer in kwargs:
kwargs['label'] = get_label(kwargs[label_namer], label)
else:
warnings.warn(
cbook._warn_external(
"Tried to set a label via parameter %r in func %r but "
"couldn't find such an argument.\n"
"(This is a programming error, please report to "
"the Matplotlib list!)" % (label_namer, func.__name__),
RuntimeWarning, stacklevel=2)
"couldn't find such an argument.\n(This is a "
"programming error, please report to the Matplotlib "
"list!)" % (label_namer, func.__name__),
RuntimeWarning)
return func(ax, *args, **kwargs)

inner.__doc__ = _add_data_doc(inner.__doc__,
Expand Down
15 changes: 8 additions & 7 deletions lib/matplotlib/_constrained_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@
# Todo: AnchoredOffsetbox connected to gridspecs or axes. This would
# be more general way to add extra-axes annotations.

import numpy as np
import logging
import warnings

import numpy as np

import matplotlib.cbook as cbook
import matplotlib._layoutbox as layoutbox

_log = logging.getLogger(__name__)
Expand Down Expand Up @@ -153,9 +154,9 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
if gs._layoutbox is not None:
gss.add(gs)
if len(gss) == 0:
warnings.warn('There are no gridspecs with layoutboxes. '
'Possibly did not call parent GridSpec with the figure= '
'keyword')
cbook._warn_external('There are no gridspecs with layoutboxes. '
'Possibly did not call parent GridSpec with the'
' figure= keyword')

if fig._layoutbox.constrained_layout_called < 1:
for gs in gss:
Expand Down Expand Up @@ -221,8 +222,8 @@ def do_constrained_layout(fig, renderer, h_pad, w_pad,
# so this does the same w/o zeroing layout.
ax._set_position(newpos, which='original')
else:
warnings.warn('constrained_layout not applied. At least '
'one axes collapsed to zero width or height.')
cbook._warn_external('constrained_layout not applied. At least '
'one axes collapsed to zero width or height.')


def _make_ghost_gridspec_slots(fig, gs):
Expand Down
8 changes: 6 additions & 2 deletions lib/matplotlib/artist.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from collections import OrderedDict, namedtuple
from functools import wraps
import inspect
import logging
import re
import warnings

Expand All @@ -12,6 +13,8 @@
from .transforms import (Bbox, IdentityTransform, Transform, TransformedBbox,
TransformedPatchPath, TransformedPath)

_log = logging.getLogger(__name__)


def allow_rasterization(draw):
"""
Expand Down Expand Up @@ -400,7 +403,7 @@ def contains(self, mouseevent):
"""
if callable(self._contains):
return self._contains(self, mouseevent)
warnings.warn("'%s' needs 'contains' method" % self.__class__.__name__)
_log.warning("'%s' needs 'contains' method" % self.__class__.__name__)
return False, {}

def set_contains(self, picker):
Expand Down Expand Up @@ -850,7 +853,8 @@ def set_rasterized(self, rasterized):
rasterized : bool or None
"""
if rasterized and not hasattr(self.draw, "_supports_rasterization"):
warnings.warn("Rasterization of '%s' will be ignored" % self)
cbook._warn_external(
"Rasterization of '%s' will be ignored" % self)

self._rasterized = rasterized

Expand Down
5 changes: 3 additions & 2 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4623,8 +4623,9 @@ def hexbin(self, x, y, C=None, gridsize=100, bins=None,
# Set normalizer if bins is 'log'
if bins == 'log':
if norm is not None:
warnings.warn("Only one of 'bins' and 'norm' arguments can be "
"supplied, ignoring bins={}".format(bins))
cbook._warn_external("Only one of 'bins' and 'norm' "
"arguments can be supplied, ignoring "
"bins={}".format(bins))
else:
norm = mcolors.LogNorm()
bins = None
Expand Down
Loading