From 34af5c637e0c3564a7ced7e3d8206122735df459 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Thu, 3 Sep 2015 17:56:40 +0300 Subject: [PATCH 1/2] Use json for the font cache instead of pickle --- lib/matplotlib/font_manager.py | 61 ++++++++++++++++++++++------------ 1 file changed, 39 insertions(+), 22 deletions(-) diff --git a/lib/matplotlib/font_manager.py b/lib/matplotlib/font_manager.py index a0a695bf268e..d9a449eb8c08 100644 --- a/lib/matplotlib/font_manager.py +++ b/lib/matplotlib/font_manager.py @@ -23,7 +23,6 @@ unicode_literals) from matplotlib.externals import six -from matplotlib.externals.six.moves import cPickle as pickle """ KNOWN ISSUES @@ -47,6 +46,7 @@ see license/LICENSE_TTFQUERY. """ +import json import os, sys, warnings try: set @@ -947,23 +947,43 @@ def ttfdict_to_fnames(d): fnames.append(fname) return fnames -def pickle_dump(data, filename): - """ - Equivalent to pickle.dump(data, open(filename, 'w')) - but closes the file to prevent filehandle leakage. - """ - with open(filename, 'wb') as fh: - pickle.dump(data, fh) +class JSONEncoder(json.JSONEncoder): + def default(self, o): + if isinstance(o, FontManager): + return dict(o.__dict__, _class='FontManager') + elif isinstance(o, FontEntry): + return dict(o.__dict__, _class='FontEntry') + else: + return super(JSONEncoder, self).default(o) + +def _json_decode(o): + cls = o.pop('_class', None) + if cls is None: + return o + elif cls == 'FontManager': + r = FontManager.__new__(FontManager) + r.__dict__.update(o) + return r + elif cls == 'FontEntry': + r = FontEntry.__new__(FontEntry) + r.__dict__.update(o) + return r + else: + raise ValueError("don't know how to deserialize _class=%s" % cls) -def pickle_load(filename): - """ - Equivalent to pickle.load(open(filename, 'r')) - but closes the file to prevent filehandle leakage. - """ - with open(filename, 'rb') as fh: - data = pickle.load(fh) - return data +def json_dump(data, filename): + """Dumps a data structure as JSON in the named file. + Handles FontManager and its fields.""" + + with open(filename, 'w') as fh: + json.dump(data, fh, cls=JSONEncoder, indent=2) +def json_load(filename): + """Loads a data structure as JSON from the named file. + Handles FontManager and its fields.""" + + with open(filename, 'r') as fh: + return json.load(fh, object_hook=_json_decode) class TempCache(object): """ @@ -1388,10 +1408,7 @@ def findfont(prop, fontext='ttf'): if not 'TRAVIS' in os.environ: cachedir = get_cachedir() if cachedir is not None: - if six.PY3: - _fmcache = os.path.join(cachedir, 'fontList.py3k.cache') - else: - _fmcache = os.path.join(cachedir, 'fontList.cache') + _fmcache = os.path.join(cachedir, 'fontList.json') fontManager = None @@ -1404,12 +1421,12 @@ def _rebuild(): global fontManager fontManager = FontManager() if _fmcache: - pickle_dump(fontManager, _fmcache) + json_dump(fontManager, _fmcache) verbose.report("generated new fontManager") if _fmcache: try: - fontManager = pickle_load(_fmcache) + fontManager = json_load(_fmcache) if (not hasattr(fontManager, '_version') or fontManager._version != FontManager.__version__): _rebuild() From 3008a7a481b82632abf3f35a42c423589b4c8e40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jouni=20K=2E=20Sepp=C3=A4nen?= Date: Thu, 3 Sep 2015 21:29:25 +0300 Subject: [PATCH 2/2] Test case of FontManager serialization --- lib/matplotlib/tests/test_font_manager.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/matplotlib/tests/test_font_manager.py b/lib/matplotlib/tests/test_font_manager.py index dac0710fd855..19e9c9f2a4e1 100644 --- a/lib/matplotlib/tests/test_font_manager.py +++ b/lib/matplotlib/tests/test_font_manager.py @@ -5,8 +5,11 @@ from matplotlib.externals import six import os +import tempfile +import warnings -from matplotlib.font_manager import findfont, FontProperties +from matplotlib.font_manager import ( + findfont, FontProperties, fontManager, json_dump, json_load) from matplotlib import rc_context @@ -17,3 +20,17 @@ def test_font_priority(): font = findfont( FontProperties(family=["sans-serif"])) assert_equal(os.path.basename(font), 'cmmi10.ttf') + + +def test_json_serialization(): + with tempfile.NamedTemporaryFile() as temp: + json_dump(fontManager, temp.name) + copy = json_load(temp.name) + with warnings.catch_warnings(): + warnings.filterwarnings('ignore', 'findfont: Font family.*not found') + for prop in ({'family': 'STIXGeneral'}, + {'family': 'Bitstream Vera Sans', 'weight': 700}, + {'family': 'no such font family'}): + fp = FontProperties(**prop) + assert_equal(fontManager.findfont(fp, rebuild_if_missing=False), + copy.findfont(fp, rebuild_if_missing=False))