Skip to content

Commit 2766d49

Browse files
committed
Better error messages
1 parent 37cf8e4 commit 2766d49

File tree

2 files changed

+92
-29
lines changed

2 files changed

+92
-29
lines changed

lib/matplotlib/backends/backend_svg.py

+57-29
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,24 @@ def generate_css(attrib=None):
298298
_capstyle_d = {'projecting': 'square', 'butt': 'butt', 'round': 'round'}
299299

300300

301+
def _check_is_str(info, key):
302+
if not isinstance(info, str):
303+
raise TypeError(f'Invalid type for {key} metadata. Expected str, not '
304+
f'{type(info)}.')
305+
306+
307+
def _check_is_iterable_of_str(infos, key):
308+
for info in infos:
309+
if not isinstance(info, str):
310+
raise TypeError(f'Invalid type for {key} metadata. Expected '
311+
f'iterable of str, not {type(info)}.')
312+
313+
314+
def _raise_not_str_or_iterable_of_str(infos, key):
315+
raise TypeError(f'Invalid type for {key} metadata. Expected str or '
316+
f'iterable of str, not {type(infos)}.')
317+
318+
301319
class RendererSVG(RendererBase):
302320
def __init__(self, width, height, svgwriter, basename=None, image_dpi=72,
303321
*, metadata=None):
@@ -359,7 +377,9 @@ def _write_metadata(self, metadata):
359377
writer = self.writer
360378

361379
if 'Title' in metadata:
362-
writer.element('title', text=metadata['Title'])
380+
title = metadata['Title']
381+
_check_is_str(title, 'Title')
382+
writer.element('title', text=title)
363383

364384
# Special handling.
365385
date = metadata.get('Date', None)
@@ -376,14 +396,14 @@ def _write_metadata(self, metadata):
376396
elif isinstance(d, (datetime.datetime, datetime.date)):
377397
dates.append(d.isoformat())
378398
else:
379-
raise ValueError(
380-
'Invalid type for Date metadata. '
381-
'Expected iterable of str, date, or datetime, '
382-
'not {!r}.'.format(type(d)))
399+
raise TypeError(
400+
f'Invalid type for Date metadata. '
401+
f'Expected iterable of str, date, or datetime, '
402+
f'not {type(d)}.')
383403
else:
384-
raise ValueError('Invalid type for Date metadata. '
385-
'Expected str, date, datetime, or iterable '
386-
'of the same, not {!r}.'.format(type(date)))
404+
raise TypeError(f'Invalid type for Date metadata. '
405+
f'Expected str, date, datetime, or iterable '
406+
f'of the same, not {type(date)}.')
387407
metadata['Date'] = '/'.join(dates)
388408
elif 'Date' not in metadata:
389409
# Do not add `Date` if the user explicitly set `Date` to `None`
@@ -415,43 +435,51 @@ def ensure_metadata(mid):
415435
writer.element('dc:type', attrib={'rdf:resource': uri})
416436

417437
# Single value only.
418-
for key in ['title', 'coverage', 'date', 'description', 'format',
419-
'identifier', 'language', 'relation', 'source']:
420-
info = metadata.pop(key.title(), None)
438+
for key in ['Title', 'Coverage', 'Date', 'Description', 'Format',
439+
'Identifier', 'Language', 'Relation', 'Source']:
440+
info = metadata.pop(key, None)
421441
if info is not None:
422442
mid = ensure_metadata(mid)
423-
writer.element(f'dc:{key}', text=info)
443+
_check_is_str(info, key)
444+
writer.element(f'dc:{key.lower()}', text=info)
424445

425446
# Multiple Agent values.
426-
for key in ['creator', 'contributor', 'publisher', 'rights']:
427-
agents = metadata.pop(key.title(), None)
447+
for key in ['Creator', 'Contributor', 'Publisher', 'Rights']:
448+
agents = metadata.pop(key, None)
428449
if agents is None:
429450
continue
430451

431452
if isinstance(agents, str):
432453
agents = [agents]
433454

434-
mid = ensure_metadata(mid)
435-
writer.start(f'dc:{key}')
436-
for agent in agents:
437-
writer.start('cc:Agent')
438-
writer.element('dc:title', text=agent)
439-
writer.end('cc:Agent')
440-
writer.end(f'dc:{key}')
455+
if np.iterable(agents):
456+
_check_is_iterable_of_str(agents, key)
457+
mid = ensure_metadata(mid)
458+
writer.start(f'dc:{key.lower()}')
459+
for agent in agents:
460+
writer.start('cc:Agent')
461+
writer.element('dc:title', text=agent)
462+
writer.end('cc:Agent')
463+
writer.end(f'dc:{key.lower()}')
464+
else:
465+
_raise_not_str_or_iterable_of_str(agents, key)
441466

442467
# Multiple values.
443468
keywords = metadata.pop('Keywords', None)
444469
if keywords is not None:
445470
if isinstance(keywords, str):
446471
keywords = [keywords]
447-
448-
mid = ensure_metadata(mid)
449-
writer.start('dc:subject')
450-
writer.start('rdf:Bag')
451-
for keyword in keywords:
452-
writer.element('rdf:li', text=keyword)
453-
writer.end('rdf:Bag')
454-
writer.end('dc:subject')
472+
if np.iterable(keywords):
473+
_check_is_iterable_of_str(keywords, 'Keywords')
474+
mid = ensure_metadata(mid)
475+
writer.start('dc:subject')
476+
writer.start('rdf:Bag')
477+
for keyword in keywords:
478+
writer.element('rdf:li', text=keyword)
479+
writer.end('rdf:Bag')
480+
writer.end('dc:subject')
481+
else:
482+
_raise_not_str_or_iterable_of_str(keywords, 'Keywords')
455483

456484
if mid is not None:
457485
writer.close(mid)

lib/matplotlib/tests/test_backend_svg.py

+35
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import xml.parsers.expat
55

66
import numpy as np
7+
import pytest
78

89
import matplotlib as mpl
910
from matplotlib.figure import Figure
@@ -464,3 +465,37 @@ def test_svg_metadata():
464465
values = [node.text for node in
465466
rdf.findall(f'./{CCNS}Work/{DCNS}subject/{RDFNS}Bag/{RDFNS}li')]
466467
assert values == metadata['Keywords']
468+
469+
470+
@pytest.mark.parametrize('metadata,error,message', [
471+
({'Date': 1}, TypeError, "Invalid type for Date metadata. Expected str"),
472+
({'Date': [1]}, TypeError,
473+
"Invalid type for Date metadata. Expected iterable"),
474+
({'Keywords': 1}, TypeError,
475+
"Invalid type for Keywords metadata. Expected str"),
476+
({'Keywords': [1]}, TypeError,
477+
"Invalid type for Keywords metadata. Expected iterable"),
478+
({'Creator': 1}, TypeError,
479+
"Invalid type for Creator metadata. Expected str"),
480+
({'Creator': [1]}, TypeError,
481+
"Invalid type for Creator metadata. Expected iterable"),
482+
({'Title': 1}, TypeError,
483+
"Invalid type for Title metadata. Expected str"),
484+
({'Format': 1}, TypeError,
485+
"Invalid type for Format metadata. Expected str"),
486+
({'Foo': 'Bar'}, ValueError, "Unknown metadata key"),
487+
])
488+
def test_svg_incorrect_metadata(metadata, error, message):
489+
with pytest.raises(error, match=message), BytesIO() as fd:
490+
fig, ax = plt.subplots()
491+
fig.savefig(fd, format='svg', metadata=metadata)
492+
493+
494+
def test_svg_escape():
495+
fig, ax = plt.subplots()
496+
fig.text(0.5, 0.5, "<\'\"&>", gid="<\'\"&>")
497+
ax.set_axis_off()
498+
with BytesIO() as fd:
499+
fig.savefig(fd, format='svg')
500+
buf = fd.getvalue().decode()
501+
assert '&lt;&apos;&quot;&amp;&gt;"' in buf

0 commit comments

Comments
 (0)