Skip to content

Simplify retrieval of cache and config directories #11398

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
merged 3 commits into from
Jul 9, 2018
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
157 changes: 64 additions & 93 deletions lib/matplotlib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,6 @@ def compare_versions(a, b):
sys.argv = ['modpython']


def _is_writable_dir(p):
"""
p is a string pointing to a putative writable dir -- return True p
is such a string, else False
"""
return os.access(p, os.W_OK) and os.path.isdir(p)

_verbose_msg = """\
matplotlib.verbose is deprecated;
Command line argument --verbose-LEVEL is deprecated.
Expand Down Expand Up @@ -393,27 +386,34 @@ def ge(self, level):
verbose = Verbose()


def _wrap(fmt, func, level=logging.DEBUG, always=True):
def _logged_cached(fmt, func=None):
"""
return a callable function that wraps func and reports its
output through logger
Decorator that logs a function's return value, and memoizes that value.

if always is True, the report will occur on every function
call; otherwise only on the first time the function is called
"""
assert callable(func)
After ::

def wrapper(*args, **kwargs):
ret = func(*args, **kwargs)
@_logged_cached(fmt)
def func(): ...

if (always or not wrapper._spoke):
_log.log(level, fmt % ret)
spoke = True
if not wrapper._spoke:
wrapper._spoke = spoke
the first call to *func* will log its return value at the DEBUG level using
%-format string *fmt*, and memoize it; later calls to *func* will directly
return that value.
"""
if func is None: # Return the actual decorator.
return functools.partial(_logged_cached, fmt)

called = False
ret = None

@functools.wraps(func)
def wrapper():
nonlocal called, ret
if not called:
ret = func()
called = True
_log.debug(fmt, ret)
return ret
wrapper._spoke = False
wrapper.__doc__ = func.__doc__

return wrapper


Expand Down Expand Up @@ -551,49 +551,39 @@ def checkdep_usetex(s):
return flag


def _get_home():
"""Find user's home directory if possible.
Otherwise, returns None.
@_logged_cached('$HOME=%s')
def get_home():
"""
Return the user's home directory.

:see:
http://mail.python.org/pipermail/python-list/2005-February/325395.html
If the user's home directory cannot be found, return None.
"""
path = os.path.expanduser("~")
if os.path.isdir(path):
return path
for evar in ('HOME', 'USERPROFILE', 'TMP'):
path = os.environ.get(evar)
if path is not None and os.path.isdir(path):
return path
return None
try:
return str(Path.home())
except Exception:
return None


def _create_tmp_config_dir():
"""
If the config directory can not be created, create a temporary
directory.
If the config directory can not be created, create a temporary directory.
"""
configdir = os.environ['MPLCONFIGDIR'] = (
tempfile.mkdtemp(prefix='matplotlib-'))
atexit.register(shutil.rmtree, configdir)
return configdir


get_home = _wrap('$HOME=%s', _get_home, always=False)


def _get_xdg_config_dir():
"""
Returns the XDG configuration directory, according to the `XDG
base directory spec
<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_.
"""
path = os.environ.get('XDG_CONFIG_HOME')
if path is None:
path = get_home()
if path is not None:
path = os.path.join(path, '.config')
return path
return (os.environ.get('XDG_CONFIG_HOME')
or (str(Path(get_home(), ".config"))
if get_home()
else None))


def _get_xdg_cache_dir():
Expand All @@ -602,60 +592,46 @@ def _get_xdg_cache_dir():
base directory spec
<http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html>`_.
"""
path = os.environ.get('XDG_CACHE_HOME')
if path is None:
path = get_home()
if path is not None:
path = os.path.join(path, '.cache')
return path
return (os.environ.get('XDG_CACHE_HOME')
or (str(Path(get_home(), ".cache"))
if get_home()
else None))


def _get_config_or_cache_dir(xdg_base):
configdir = os.environ.get('MPLCONFIGDIR')
if configdir is not None:
configdir = os.path.abspath(configdir)
Path(configdir).mkdir(parents=True, exist_ok=True)
if not _is_writable_dir(configdir):
return _create_tmp_config_dir()
return configdir

p = None
h = get_home()
if h is not None:
p = os.path.join(h, '.matplotlib')
if sys.platform.startswith(('linux', 'freebsd')):
p = None
if xdg_base is not None:
p = os.path.join(xdg_base, 'matplotlib')

if p is not None:
if os.path.exists(p):
if _is_writable_dir(p):
return p
if configdir:
configdir = Path(configdir).resolve()
elif sys.platform.startswith(('linux', 'freebsd')) and xdg_base:
configdir = Path(xdg_base, "matplotlib")
elif get_home():
configdir = Path(get_home(), ".matplotlib")
else:
configdir = None

if configdir:
try:
configdir.mkdir(parents=True, exist_ok=True)
except OSError:
pass
else:
try:
Path(p).mkdir(parents=True, exist_ok=True)
except OSError:
pass
else:
return p
if os.access(str(configdir), os.W_OK) and configdir.is_dir():
return str(configdir)

return _create_tmp_config_dir()


def _get_configdir():
@_logged_cached('CONFIGDIR=%s')
def get_configdir():
"""
Return the string representing the configuration directory.

The directory is chosen as follows:

1. If the MPLCONFIGDIR environment variable is supplied, choose that.

2a. On Linux, follow the XDG specification and look first in
`$XDG_CONFIG_HOME`, if defined, or `$HOME/.config`.

2b. On other platforms, choose `$HOME/.matplotlib`.

3. If the chosen directory exists and is writable, use that as the
configuration directory.
4. If possible, create a temporary directory, and use it as the
Expand All @@ -664,10 +640,9 @@ def _get_configdir():
"""
return _get_config_or_cache_dir(_get_xdg_config_dir())

get_configdir = _wrap('CONFIGDIR=%s', _get_configdir, always=False)


def _get_cachedir():
@_logged_cached('CACHEDIR=%s')
def get_cachedir():
"""
Return the location of the cache directory.

Expand All @@ -676,8 +651,6 @@ def _get_cachedir():
"""
return _get_config_or_cache_dir(_get_xdg_cache_dir())

get_cachedir = _wrap('CACHEDIR=%s', _get_cachedir, always=False)


def _decode_filesystem_path(path):
if not isinstance(path, str):
Expand Down Expand Up @@ -729,14 +702,12 @@ def _get_data_path():
raise RuntimeError('Could not find the matplotlib data files')


def _get_data_path_cached():
@_logged_cached('matplotlib data path: %s')
def get_data_path():
if defaultParams['datapath'][0] is None:
defaultParams['datapath'][0] = _get_data_path()
return defaultParams['datapath'][0]

get_data_path = _wrap('matplotlib data path %s', _get_data_path_cached,
always=False)


def get_py2exe_datafiles():
data_path = Path(get_data_path())
Expand Down Expand Up @@ -787,7 +758,7 @@ def gen_candidates():
else:
yield matplotlibrc
yield os.path.join(matplotlibrc, 'matplotlibrc')
yield os.path.join(_get_configdir(), 'matplotlibrc')
yield os.path.join(get_configdir(), 'matplotlibrc')
yield os.path.join(get_data_path(), 'matplotlibrc')

for fname in gen_candidates():
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/cbook/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1984,7 +1984,7 @@ class so far, an alias named ``get_alias`` will be defined; the same will
can be used by `~.normalize_kwargs` (which assumes that higher priority
aliases come last).
"""
if cls is None:
if cls is None: # Return the actual class decorator.
return functools.partial(_define_aliases, alias_d)

def make_alias(name): # Enforce a closure over *name*.
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/style/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

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

Expand Down
17 changes: 6 additions & 11 deletions lib/matplotlib/testing/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@

import matplotlib
from matplotlib.testing.exceptions import ImageComparisonFailure
from matplotlib import _png
from matplotlib import _get_cachedir
from matplotlib import cbook
from matplotlib import _png, cbook

__all__ = ['compare_float', 'compare_images', 'comparable_formats']

Expand Down Expand Up @@ -79,7 +77,7 @@ def compare_float(expected, actual, relTol=None, absTol=None):


def get_cache_dir():
cachedir = _get_cachedir()
cachedir = matplotlib.get_cachedir()
if cachedir is None:
raise RuntimeError('Could not find a suitable configuration directory')
cache_dir = os.path.join(cachedir, 'test_cache')
Expand Down Expand Up @@ -293,15 +291,12 @@ def comparable_formats():

def convert(filename, cache):
"""
Convert the named file into a png file. Returns the name of the
created file.
Convert the named file to png; return the name of the created file.

If *cache* is True, the result of the conversion is cached in
`matplotlib._get_cachedir() + '/test_cache/'`. The caching is based
on a hash of the exact contents of the input file. The is no limit
on the size of the cache, so it may need to be manually cleared
periodically.

`matplotlib.get_cachedir() + '/test_cache/'`. The caching is based on a
hash of the exact contents of the input file. There is no limit on the
size of the cache, so it may need to be manually cleared periodically.
"""
base, extension = filename.rsplit('.', 1)
if extension not in converter:
Expand Down