test_pylabtools.py
375 lines
| 11.5 KiB
| text/x-python
|
PythonLexer
Fernando Perez
|
r3734 | """Tests for pylab tools module. | ||
""" | ||||
Min RK
|
r21765 | |||
# Copyright (c) IPython Development Team. | ||||
Fernando Perez
|
r3734 | # Distributed under the terms of the Modified BSD License. | ||
Min RK
|
r26812 | from binascii import a2b_base64 | ||
from io import BytesIO | ||||
MinRK
|
r15684 | |||
Nikita Kniazev
|
r26998 | import pytest | ||
matplotlib = pytest.importorskip("matplotlib") | ||||
MinRK
|
r15390 | matplotlib.use('Agg') | ||
from matplotlib.figure import Figure | ||||
Fernando Perez
|
r3734 | |||
from matplotlib import pyplot as plt | ||||
Samuel Gaist
|
r26907 | from matplotlib_inline import backend_inline | ||
Fernando Perez
|
r5468 | import numpy as np | ||
Fernando Perez
|
r3734 | |||
MinRK
|
r15390 | from IPython.core.getipython import get_ipython | ||
MinRK
|
r11329 | from IPython.core.interactiveshell import InteractiveShell | ||
MinRK
|
r15390 | from IPython.core.display import _PNG, _JPEG | ||
Fernando Perez
|
r3734 | from .. import pylabtools as pt | ||
Daniel B. Vasquez
|
r14777 | from IPython.testing import decorators as dec | ||
Fernando Perez
|
r3734 | |||
def test_figure_to_svg(): | ||||
# simple empty-figure test | ||||
fig = plt.figure() | ||||
Samuel Gaist
|
r26907 | assert pt.print_figure(fig, "svg") is None | ||
Fernando Perez
|
r3734 | |||
plt.close('all') | ||||
# simple check for at least svg-looking output | ||||
Fernando Perez
|
r3745 | fig = plt.figure() | ||
ax = fig.add_subplot(1,1,1) | ||||
Fernando Perez
|
r3734 | ax.plot([1,2,3]) | ||
plt.draw() | ||||
Samuel Gaist
|
r26907 | svg = pt.print_figure(fig, "svg")[:100].lower() | ||
assert "doctype svg" in svg | ||||
Fernando Perez
|
r5468 | |||
MinRK
|
r15684 | def _check_pil_jpeg_bytes(): | ||
"""Skip if PIL can't write JPEGs to BytesIO objects""" | ||||
MinRK
|
r15686 | # PIL's JPEG plugin can't write to BytesIO objects | ||
# Pillow fixes this | ||||
MinRK
|
r15684 | from PIL import Image | ||
buf = BytesIO() | ||||
img = Image.new("RGB", (4,4)) | ||||
try: | ||||
img.save(buf, 'jpeg') | ||||
except Exception as e: | ||||
ename = e.__class__.__name__ | ||||
Samuel Gaist
|
r26907 | raise pytest.skip("PIL can't write JPEG to BytesIO: %s: %s" % (ename, e)) from e | ||
MinRK
|
r15684 | |||
Daniel B. Vasquez
|
r14777 | @dec.skip_without("PIL.Image") | ||
MinRK
|
r15684 | def test_figure_to_jpeg(): | ||
_check_pil_jpeg_bytes() | ||||
# simple check for at least jpeg-looking output | ||||
Daniel B. Vasquez
|
r14777 | fig = plt.figure() | ||
ax = fig.add_subplot(1,1,1) | ||||
ax.plot([1,2,3]) | ||||
plt.draw() | ||||
Matthias Bussonnier
|
r25633 | jpeg = pt.print_figure(fig, 'jpeg', pil_kwargs={'optimize': 50})[:100].lower() | ||
MinRK
|
r15684 | assert jpeg.startswith(_JPEG) | ||
Daniel B. Vasquez
|
r14775 | |||
MinRK
|
r15390 | def test_retina_figure(): | ||
Christopher Roach
|
r21813 | # simple empty-figure test | ||
fig = plt.figure() | ||||
Samuel Gaist
|
r26907 | assert pt.retina_figure(fig) == None | ||
Christopher Roach
|
r21813 | plt.close('all') | ||
MinRK
|
r15390 | fig = plt.figure() | ||
ax = fig.add_subplot(1,1,1) | ||||
ax.plot([1,2,3]) | ||||
plt.draw() | ||||
png, md = pt.retina_figure(fig) | ||||
assert png.startswith(_PNG) | ||||
Samuel Gaist
|
r26907 | assert "width" in md | ||
assert "height" in md | ||||
MinRK
|
r15390 | |||
_fmt_mime_map = { | ||||
'png': 'image/png', | ||||
'jpeg': 'image/jpeg', | ||||
'pdf': 'application/pdf', | ||||
'retina': 'image/png', | ||||
'svg': 'image/svg+xml', | ||||
} | ||||
def test_select_figure_formats_str(): | ||||
ip = get_ipython() | ||||
for fmt, active_mime in _fmt_mime_map.items(): | ||||
pt.select_figure_formats(ip, fmt) | ||||
for mime, f in ip.display_formatter.formatters.items(): | ||||
if mime == active_mime: | ||||
Samuel Gaist
|
r26907 | assert Figure in f | ||
MinRK
|
r15390 | else: | ||
Samuel Gaist
|
r26907 | assert Figure not in f | ||
MinRK
|
r15390 | |||
def test_select_figure_formats_kwargs(): | ||||
ip = get_ipython() | ||||
Nikita Kniazev
|
r27009 | kwargs = dict(bbox_inches="tight") | ||
pt.select_figure_formats(ip, "png", **kwargs) | ||||
formatter = ip.display_formatter.formatters["image/png"] | ||||
MinRK
|
r15390 | f = formatter.lookup_by_type(Figure) | ||
Min RK
|
r26812 | cell = f.keywords | ||
expected = kwargs | ||||
expected["base64"] = True | ||||
expected["fmt"] = "png" | ||||
assert cell == expected | ||||
Thomas A Caswell
|
r24290 | |||
MinRK
|
r15390 | # check that the formatter doesn't raise | ||
fig = plt.figure() | ||||
ax = fig.add_subplot(1,1,1) | ||||
ax.plot([1,2,3]) | ||||
plt.draw() | ||||
formatter.enabled = True | ||||
png = formatter(fig) | ||||
Min RK
|
r26812 | assert isinstance(png, str) | ||
png_bytes = a2b_base64(png) | ||||
assert png_bytes.startswith(_PNG) | ||||
Fernando Perez
|
r5468 | |||
MinRK
|
r15390 | def test_select_figure_formats_set(): | ||
Fernando Perez
|
r5468 | ip = get_ipython() | ||
MinRK
|
r15390 | for fmts in [ | ||
{'png', 'svg'}, | ||||
['png'], | ||||
('jpeg', 'pdf', 'retina'), | ||||
{'svg'}, | ||||
]: | ||||
active_mimes = {_fmt_mime_map[fmt] for fmt in fmts} | ||||
pt.select_figure_formats(ip, fmts) | ||||
for mime, f in ip.display_formatter.formatters.items(): | ||||
if mime in active_mimes: | ||||
Samuel Gaist
|
r26907 | assert Figure in f | ||
MinRK
|
r15390 | else: | ||
Samuel Gaist
|
r26907 | assert Figure not in f | ||
MinRK
|
r15390 | |||
def test_select_figure_formats_bad(): | ||||
ip = get_ipython() | ||||
Samuel Gaist
|
r26907 | with pytest.raises(ValueError): | ||
MinRK
|
r15390 | pt.select_figure_formats(ip, 'foo') | ||
Samuel Gaist
|
r26907 | with pytest.raises(ValueError): | ||
MinRK
|
r15390 | pt.select_figure_formats(ip, {'png', 'foo'}) | ||
Samuel Gaist
|
r26907 | with pytest.raises(ValueError): | ||
MinRK
|
r15390 | pt.select_figure_formats(ip, ['retina', 'pdf', 'bar', 'bad']) | ||
def test_import_pylab(): | ||||
Fernando Perez
|
r5469 | ns = {} | ||
pt.import_pylab(ns, import_all=False) | ||||
Samuel Gaist
|
r26907 | assert "plt" in ns | ||
assert ns["np"] == np | ||||
Ryan May
|
r7966 | |||
class TestPylabSwitch(object): | ||||
MinRK
|
r11329 | class Shell(InteractiveShell): | ||
Matthias Bussonnier
|
r26428 | def init_history(self): | ||
"""Sets up the command history, and starts regular autosaves.""" | ||||
self.config.HistoryManager.hist_file = ":memory:" | ||||
super().init_history() | ||||
MinRK
|
r11329 | def enable_gui(self, gui): | ||
pass | ||||
Thomas A Caswell
|
r24290 | |||
Lumir Balhar
|
r28734 | def setup_method(self): | ||
Ryan May
|
r8003 | import matplotlib | ||
def act_mpl(backend): | ||||
matplotlib.rcParams['backend'] = backend | ||||
# Save rcParams since they get modified | ||||
self._saved_rcParams = matplotlib.rcParams | ||||
Thomas Kluyver
|
r12924 | self._saved_rcParamsOrig = matplotlib.rcParamsOrig | ||
Thomas A Caswell
|
r28351 | matplotlib.rcParams = dict(backend="QtAgg") | ||
matplotlib.rcParamsOrig = dict(backend="QtAgg") | ||||
Ryan May
|
r8003 | |||
# Mock out functions | ||||
Ryan May
|
r7966 | self._save_am = pt.activate_matplotlib | ||
Ryan May
|
r8003 | pt.activate_matplotlib = act_mpl | ||
Ryan May
|
r7966 | self._save_ip = pt.import_pylab | ||
pt.import_pylab = lambda *a,**kw:None | ||||
Samuel Gaist
|
r26907 | self._save_cis = backend_inline.configure_inline_support | ||
backend_inline.configure_inline_support = lambda *a, **kw: None | ||||
Ryan May
|
r7966 | |||
Lumir Balhar
|
r28734 | def teardown_method(self): | ||
Ryan May
|
r7966 | pt.activate_matplotlib = self._save_am | ||
pt.import_pylab = self._save_ip | ||||
Samuel Gaist
|
r26907 | backend_inline.configure_inline_support = self._save_cis | ||
Ryan May
|
r8003 | import matplotlib | ||
matplotlib.rcParams = self._saved_rcParams | ||||
Thomas Kluyver
|
r12924 | matplotlib.rcParamsOrig = self._saved_rcParamsOrig | ||
Ryan May
|
r7966 | |||
def test_qt(self): | ||||
s = self.Shell() | ||||
MinRK
|
r11329 | gui, backend = s.enable_matplotlib(None) | ||
Samuel Gaist
|
r26907 | assert gui == "qt" | ||
assert s.pylab_gui_select == "qt" | ||||
Ryan May
|
r7966 | |||
Samuel Gaist
|
r26907 | gui, backend = s.enable_matplotlib("inline") | ||
Ian Thomas
|
r28714 | assert gui is None | ||
Samuel Gaist
|
r26907 | assert s.pylab_gui_select == "qt" | ||
Ryan May
|
r7966 | |||
Samuel Gaist
|
r26907 | gui, backend = s.enable_matplotlib("qt") | ||
assert gui == "qt" | ||||
assert s.pylab_gui_select == "qt" | ||||
Ryan May
|
r7966 | |||
Samuel Gaist
|
r26907 | gui, backend = s.enable_matplotlib("inline") | ||
Ian Thomas
|
r28714 | assert gui is None | ||
Samuel Gaist
|
r26907 | assert s.pylab_gui_select == "qt" | ||
Ryan May
|
r7966 | |||
MinRK
|
r11329 | gui, backend = s.enable_matplotlib() | ||
Samuel Gaist
|
r26907 | assert gui == "qt" | ||
assert s.pylab_gui_select == "qt" | ||||
Ryan May
|
r7966 | |||
def test_inline(self): | ||||
s = self.Shell() | ||||
Samuel Gaist
|
r26907 | gui, backend = s.enable_matplotlib("inline") | ||
Ian Thomas
|
r28714 | assert gui is None | ||
Samuel Gaist
|
r26907 | assert s.pylab_gui_select == None | ||
Ryan May
|
r7966 | |||
Samuel Gaist
|
r26907 | gui, backend = s.enable_matplotlib("inline") | ||
Ian Thomas
|
r28714 | assert gui is None | ||
Samuel Gaist
|
r26907 | assert s.pylab_gui_select == None | ||
Ryan May
|
r7966 | |||
Samuel Gaist
|
r26907 | gui, backend = s.enable_matplotlib("qt") | ||
assert gui == "qt" | ||||
assert s.pylab_gui_select == "qt" | ||||
Ryan May
|
r7966 | |||
Jan Schulz
|
r21756 | def test_inline_twice(self): | ||
"Using '%matplotlib inline' twice should not reset formatters" | ||||
Min RK
|
r21765 | ip = self.Shell() | ||
Samuel Gaist
|
r26907 | gui, backend = ip.enable_matplotlib("inline") | ||
Ian Thomas
|
r28714 | assert gui is None | ||
Jan Schulz
|
r21756 | |||
fmts = {'png'} | ||||
active_mimes = {_fmt_mime_map[fmt] for fmt in fmts} | ||||
pt.select_figure_formats(ip, fmts) | ||||
Samuel Gaist
|
r26907 | gui, backend = ip.enable_matplotlib("inline") | ||
Ian Thomas
|
r28714 | assert gui is None | ||
Jan Schulz
|
r21756 | |||
for mime, f in ip.display_formatter.formatters.items(): | ||||
if mime in active_mimes: | ||||
Samuel Gaist
|
r26907 | assert Figure in f | ||
Jan Schulz
|
r21756 | else: | ||
Samuel Gaist
|
r26907 | assert Figure not in f | ||
Jan Schulz
|
r21756 | |||
Ryan May
|
r7966 | def test_qt_gtk(self): | ||
s = self.Shell() | ||||
Samuel Gaist
|
r26907 | gui, backend = s.enable_matplotlib("qt") | ||
assert gui == "qt" | ||||
assert s.pylab_gui_select == "qt" | ||||
Ryan May
|
r7966 | |||
Ian Thomas
|
r28714 | gui, backend = s.enable_matplotlib("gtk3") | ||
Samuel Gaist
|
r26907 | assert gui == "qt" | ||
assert s.pylab_gui_select == "qt" | ||||
Ryan May
|
r7966 | |||
Ian Thomas
|
r28812 | @dec.skipif(not pt._matplotlib_manages_backends()) | ||
def test_backend_module_name_case_sensitive(self): | ||||
# Matplotlib backend names are case insensitive unless explicitly specified using | ||||
# "module://some_module.some_name" syntax which are case sensitive for mpl >= 3.9.1 | ||||
all_lowercase = "module://matplotlib_inline.backend_inline" | ||||
some_uppercase = "module://matplotlib_inline.Backend_inline" | ||||
mpl3_9_1 = matplotlib.__version_info__ >= (3, 9, 1) | ||||
s = self.Shell() | ||||
s.enable_matplotlib(all_lowercase) | ||||
if mpl3_9_1: | ||||
with pytest.raises(RuntimeError): | ||||
s.enable_matplotlib(some_uppercase) | ||||
else: | ||||
s.enable_matplotlib(some_uppercase) | ||||
s.run_line_magic("matplotlib", all_lowercase) | ||||
if mpl3_9_1: | ||||
with pytest.raises(RuntimeError): | ||||
s.run_line_magic("matplotlib", some_uppercase) | ||||
else: | ||||
s.run_line_magic("matplotlib", some_uppercase) | ||||
Thomas A Caswell
|
r24290 | |||
def test_no_gui_backends(): | ||||
for k in ['agg', 'svg', 'pdf', 'ps']: | ||||
assert k not in pt.backend2gui | ||||
Thomas A Caswell
|
r25419 | |||
def test_figure_no_canvas(): | ||||
fig = Figure() | ||||
fig.canvas = None | ||||
pt.print_figure(fig) | ||||
Ian Thomas
|
r28717 | |||
@pytest.mark.parametrize( | ||||
"name, expected_gui, expected_backend", | ||||
[ | ||||
# name is gui | ||||
("gtk3", "gtk3", "gtk3agg"), | ||||
("gtk4", "gtk4", "gtk4agg"), | ||||
Ian Thomas
|
r28752 | ("headless", None, "agg"), | ||
Ian Thomas
|
r28717 | ("osx", "osx", "macosx"), | ||
("qt", "qt", "qtagg"), | ||||
("qt5", "qt5", "qt5agg"), | ||||
Ian Thomas
|
r28751 | ("qt6", "qt6", "qtagg"), | ||
Ian Thomas
|
r28717 | ("tk", "tk", "tkagg"), | ||
("wx", "wx", "wxagg"), | ||||
# name is backend | ||||
("agg", None, "agg"), | ||||
("cairo", None, "cairo"), | ||||
("pdf", None, "pdf"), | ||||
("ps", None, "ps"), | ||||
("svg", None, "svg"), | ||||
("template", None, "template"), | ||||
("gtk3agg", "gtk3", "gtk3agg"), | ||||
("gtk3cairo", "gtk3", "gtk3cairo"), | ||||
("gtk4agg", "gtk4", "gtk4agg"), | ||||
("gtk4cairo", "gtk4", "gtk4cairo"), | ||||
("macosx", "osx", "macosx"), | ||||
("nbagg", "nbagg", "nbagg"), | ||||
("notebook", "nbagg", "notebook"), | ||||
("qtagg", "qt", "qtagg"), | ||||
("qtcairo", "qt", "qtcairo"), | ||||
("qt5agg", "qt5", "qt5agg"), | ||||
("qt5cairo", "qt5", "qt5cairo"), | ||||
("tkagg", "tk", "tkagg"), | ||||
("tkcairo", "tk", "tkcairo"), | ||||
("webagg", "webagg", "webagg"), | ||||
("wxagg", "wx", "wxagg"), | ||||
("wxcairo", "wx", "wxcairo"), | ||||
], | ||||
) | ||||
def test_backend_builtin(name, expected_gui, expected_backend): | ||||
# Test correct identification of Matplotlib built-in backends without importing and using them, | ||||
# otherwise we would need to ensure all the complex dependencies such as windowing toolkits are | ||||
# installed. | ||||
mpl_manages_backends = pt._matplotlib_manages_backends() | ||||
if not mpl_manages_backends: | ||||
# Backends not supported before _matplotlib_manages_backends or supported | ||||
# but with different expected_gui or expected_backend. | ||||
if ( | ||||
name.endswith("agg") | ||||
or name.endswith("cairo") | ||||
or name in ("headless", "macosx", "pdf", "ps", "svg", "template") | ||||
): | ||||
pytest.skip() | ||||
elif name == "qt6": | ||||
expected_backend = "qtagg" | ||||
elif name == "notebook": | ||||
expected_backend, expected_gui = expected_gui, expected_backend | ||||
gui, backend = pt.find_gui_and_backend(name) | ||||
if not mpl_manages_backends: | ||||
gui = gui.lower() if gui else None | ||||
backend = backend.lower() if backend else None | ||||
assert gui == expected_gui | ||||
assert backend == expected_backend | ||||
def test_backend_entry_point(): | ||||
gui, backend = pt.find_gui_and_backend("inline") | ||||
assert gui is None | ||||
expected_backend = ( | ||||
"inline" | ||||
if pt._matplotlib_manages_backends() | ||||
else "module://matplotlib_inline.backend_inline" | ||||
) | ||||
assert backend == expected_backend | ||||
def test_backend_unknown(): | ||||
with pytest.raises(RuntimeError if pt._matplotlib_manages_backends() else KeyError): | ||||
pt.find_gui_and_backend("name-does-not-exist") | ||||