Skip to content

Commit 86f2531

Browse files
committed
Some more fixes for the verbose -> logging switch.
Technically, this PR changes the behavior of get_home(), get_cachedir(), etc. making them insensitive to changes in the relevant environment variables *after* matplotlib is imported. In practice I strongly doubt that we supported that use case to begin with (because the values are probably cached in other places); I am also happy with saying "don't do this" until someone brings up a good reason to change the cache dir in the middle of the program...
1 parent 477c1dc commit 86f2531

File tree

4 files changed

+46
-55
lines changed

4 files changed

+46
-55
lines changed

doc/devel/contributing.rst

+10-11
Original file line numberDiff line numberDiff line change
@@ -477,19 +477,18 @@ Then they will receive messages like::
477477
Which logging level to use?
478478
~~~~~~~~~~~~~~~~~~~~~~~~~~~
479479

480-
There are five levels at which you can emit messages.
481-
`logging.critical` and `logging.error`
482-
are really only there for errors that will end the use of the library but
483-
not kill the interpreter. `logging.warning` overlaps with the
484-
``warnings`` library. The
485-
`logging tutorial <https://docs.python.org/3/howto/logging.html#logging-basic-tutorial>`_
486-
suggests that the difference
487-
between `logging.warning` and `warnings.warn` is that
488-
`warnings.warn` be used for things the user must change to stop
489-
the warning, whereas `logging.warning` can be more persistent.
480+
There are five levels at which you can emit messages. `logging.critical` and
481+
`logging.error` are really only there for errors that will end the use of the
482+
library but not kill the interpreter. `logging.warning` overlaps with the
483+
`warnings` library. The `logging tutorial`_ suggests that the difference
484+
between `logging.warning` and `warnings.warn` is that `warnings.warn` be used
485+
for things the user must change to stop the warning, whereas `logging.warning`
486+
can be more persistent.
487+
488+
.. _logging tutorial: https://docs.python.org/3/howto/logging.html#logging-basic-tutorial
490489

491490
By default, `logging` displays all log messages at levels higher than
492-
`logging.WARNING` to `sys.stderr`.
491+
``logging.WARNING`` to `sys.stderr`.
493492

494493
Calls to `logging.info` are not displayed by default. They are for
495494
information that the user may want to know if the program behaves oddly.

lib/matplotlib/__init__.py

+33-40
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,11 @@
120120
import tempfile
121121
import warnings
122122

123+
try:
124+
from functools import lru_cache
125+
except ImportError:
126+
from backports.functools_lru_cache import lru_cache
127+
123128
# cbook must import matplotlib only within function
124129
# definitions, so it is safe to import from it here.
125130
from . import cbook
@@ -385,31 +390,6 @@ def ge(self, level):
385390
return self.vald[self.level] >= self.vald[level]
386391

387392

388-
def _wrap(fmt, func, level='INFO', always=True):
389-
"""
390-
return a callable function that wraps func and reports its
391-
output through logger
392-
393-
if always is True, the report will occur on every function
394-
call; otherwise only on the first time the function is called
395-
"""
396-
assert callable(func)
397-
398-
def wrapper(*args, **kwargs):
399-
ret = func(*args, **kwargs)
400-
401-
if (always or not wrapper._spoke):
402-
lvl = logging.getLevelName(level.upper())
403-
_log.log(lvl, fmt % ret)
404-
spoke = True
405-
if not wrapper._spoke:
406-
wrapper._spoke = spoke
407-
return ret
408-
wrapper._spoke = False
409-
wrapper.__doc__ = func.__doc__
410-
return wrapper
411-
412-
413393
def checkdep_dvipng():
414394
try:
415395
s = subprocess.Popen([str('dvipng'), '-version'],
@@ -578,7 +558,27 @@ def checkdep_usetex(s):
578558
return flag
579559

580560

581-
def _get_home():
561+
def _log_and_cache_result(fmt, level="INFO", func=None):
562+
"""Decorator that logs & caches the result of a function with no arguments.
563+
564+
The first time the decorated *func* is called, its result is logged at
565+
level *level* using log format *fmt*, and cached. Later calls immediately
566+
return the cached result without logging.
567+
"""
568+
if func is None:
569+
return functools.partial(_log_and_cache_result, fmt, level)
570+
571+
@lru_cache(1) # Make calls after the 1st return the cached result.
572+
@functools.wraps(func)
573+
def wrapper():
574+
retval = func()
575+
_log.log(logging.getLevelName(level.upper()), fmt, retval)
576+
return retval
577+
return wrapper
578+
579+
580+
@_log_and_cache_result("HOME=%s")
581+
def get_home():
582582
"""Find user's home directory if possible.
583583
Otherwise, returns None.
584584
@@ -608,9 +608,6 @@ def _create_tmp_config_dir():
608608
return configdir
609609

610610

611-
get_home = _wrap('$HOME=%s', _get_home, always=False)
612-
613-
614611
def _get_xdg_config_dir():
615612
"""
616613
Returns the XDG configuration directory, according to the `XDG
@@ -676,7 +673,8 @@ def _get_config_or_cache_dir(xdg_base):
676673
return _create_tmp_config_dir()
677674

678675

679-
def _get_configdir():
676+
@_log_and_cache_result("CONFIGDIR=%s")
677+
def get_configdir():
680678
"""
681679
Return the string representing the configuration directory.
682680
@@ -697,10 +695,9 @@ def _get_configdir():
697695
"""
698696
return _get_config_or_cache_dir(_get_xdg_config_dir())
699697

700-
get_configdir = _wrap('CONFIGDIR=%s', _get_configdir, always=False)
701-
702698

703-
def _get_cachedir():
699+
@_log_and_cache_result("CACHEDIR=%s")
700+
def get_cachedir():
704701
"""
705702
Return the location of the cache directory.
706703
@@ -709,8 +706,6 @@ def _get_cachedir():
709706
"""
710707
return _get_config_or_cache_dir(_get_xdg_cache_dir())
711708

712-
get_cachedir = _wrap('CACHEDIR=%s', _get_cachedir, always=False)
713-
714709

715710
def _decode_filesystem_path(path):
716711
if isinstance(path, bytes):
@@ -762,14 +757,12 @@ def _get_data_path():
762757
raise RuntimeError('Could not find the matplotlib data files')
763758

764759

765-
def _get_data_path_cached():
760+
@_log_and_cache_result('rcParams["datapath"]=%s')
761+
def get_data_path():
766762
if defaultParams['datapath'][0] is None:
767763
defaultParams['datapath'][0] = _get_data_path()
768764
return defaultParams['datapath'][0]
769765

770-
get_data_path = _wrap('matplotlib data path %s', _get_data_path_cached,
771-
always=False)
772-
773766

774767
def get_py2exe_datafiles():
775768
datapath = get_data_path()
@@ -826,7 +819,7 @@ def gen_candidates():
826819
else:
827820
yield matplotlibrc
828821
yield os.path.join(matplotlibrc, 'matplotlibrc')
829-
yield os.path.join(_get_configdir(), 'matplotlibrc')
822+
yield os.path.join(get_configdir(), 'matplotlibrc')
830823
yield os.path.join(get_data_path(), 'matplotlibrc')
831824

832825
for fname in gen_candidates():

lib/matplotlib/style/core.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030

3131
BASE_LIBRARY_PATH = os.path.join(mpl.get_data_path(), 'stylelib')
3232
# Users may want multiple library paths, so store a list of paths.
33-
USER_LIBRARY_PATHS = [os.path.join(mpl._get_configdir(), 'stylelib')]
33+
USER_LIBRARY_PATHS = [os.path.join(mpl.get_configdir(), 'stylelib')]
3434
STYLE_EXTENSION = 'mplstyle'
3535
STYLE_FILE_PATTERN = re.compile(r'([\S]+).%s$' % STYLE_EXTENSION)
3636

lib/matplotlib/testing/compare.py

+2-3
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
from matplotlib.compat import subprocess
2424
from matplotlib.testing.exceptions import ImageComparisonFailure
2525
from matplotlib import _png
26-
from matplotlib import _get_cachedir
2726
from matplotlib import cbook
2827

2928
__all__ = ['compare_float', 'compare_images', 'comparable_formats']
@@ -82,7 +81,7 @@ def compare_float(expected, actual, relTol=None, absTol=None):
8281

8382

8483
def get_cache_dir():
85-
cachedir = _get_cachedir()
84+
cachedir = matplotlib.get_cachedir()
8685
if cachedir is None:
8786
raise RuntimeError('Could not find a suitable configuration directory')
8887
cache_dir = os.path.join(cachedir, 'test_cache')
@@ -273,7 +272,7 @@ def convert(filename, cache):
273272
created file.
274273
275274
If *cache* is True, the result of the conversion is cached in
276-
`matplotlib._get_cachedir() + '/test_cache/'`. The caching is based
275+
`matplotlib.get_cachedir() + '/test_cache/'`. The caching is based
277276
on a hash of the exact contents of the input file. The is no limit
278277
on the size of the cache, so it may need to be manually cleared
279278
periodically.

0 commit comments

Comments
 (0)