Skip to content

Use json for the font cache instead of pickle #5021

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 2 commits into from
Oct 6, 2015
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
61 changes: 39 additions & 22 deletions lib/matplotlib/font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
unicode_literals)

from matplotlib.externals import six
from matplotlib.externals.six.moves import cPickle as pickle

"""
KNOWN ISSUES
Expand All @@ -47,6 +46,7 @@
see license/LICENSE_TTFQUERY.
"""

import json
import os, sys, warnings
try:
set
Expand Down Expand Up @@ -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):
"""
Expand Down Expand Up @@ -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

Expand All @@ -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()
Expand Down
19 changes: 18 additions & 1 deletion lib/matplotlib/tests/test_font_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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))