Skip to content
This repository has been archived by the owner on Apr 9, 2024. It is now read-only.

Commit

Permalink
ENH: NodeSaver: suppress unhelpful vega CLI errors
Browse files Browse the repository at this point in the history
  • Loading branch information
jakevdp committed Mar 27, 2020
1 parent 69fa529 commit 5d1d895
Show file tree
Hide file tree
Showing 2 changed files with 74 additions and 14 deletions.
48 changes: 35 additions & 13 deletions altair_saver/savers/_node.py
@@ -1,7 +1,7 @@
import functools
import json
import shutil
from typing import Dict, List
from typing import Callable, Dict, List, Optional
import warnings

from altair_saver.types import JSONDict, MimebundleContent
Expand Down Expand Up @@ -34,33 +34,49 @@ def exec_path(name: str) -> str:
raise ExecutableNotFound(name)


def vl2vg(spec: JSONDict) -> JSONDict:
def vl2vg(
spec: JSONDict, stderr_filter: Optional[Callable[[str], bool]] = None
) -> JSONDict:
"""Compile a Vega-Lite spec into a Vega spec."""
vl2vg = exec_path("vl2vg")
vl_json = json.dumps(spec).encode()
vg_json = check_output_with_stderr([vl2vg], input=vl_json)
vg_json = check_output_with_stderr(
[vl2vg], input=vl_json, stderr_filter=stderr_filter
)
return json.loads(vg_json)


def vg2png(spec: JSONDict) -> bytes:
def vg2png(
spec: JSONDict, stderr_filter: Optional[Callable[[str], bool]] = None
) -> bytes:
"""Generate a PNG image from a Vega spec."""
vg2png = exec_path("vg2png")
vg_json = json.dumps(spec).encode()
return check_output_with_stderr([vg2png], input=vg_json)
return check_output_with_stderr(
[vg2png], input=vg_json, stderr_filter=stderr_filter
)


def vg2pdf(spec: JSONDict) -> bytes:
def vg2pdf(
spec: JSONDict, stderr_filter: Optional[Callable[[str], bool]] = None
) -> bytes:
"""Generate a PDF image from a Vega spec."""
vg2pdf = exec_path("vg2pdf")
vg_json = json.dumps(spec).encode()
return check_output_with_stderr([vg2pdf], input=vg_json)
return check_output_with_stderr(
[vg2pdf], input=vg_json, stderr_filter=stderr_filter
)


def vg2svg(spec: JSONDict) -> str:
def vg2svg(
spec: JSONDict, stderr_filter: Optional[Callable[[str], bool]] = None
) -> str:
"""Generate an SVG image from a Vega spec."""
vg2svg = exec_path("vg2svg")
vg_json = json.dumps(spec).encode()
return check_output_with_stderr([vg2svg], input=vg_json).decode()
return check_output_with_stderr(
[vg2svg], input=vg_json, stderr_filter=stderr_filter
).decode()


class NodeSaver(Saver):
Expand All @@ -70,6 +86,12 @@ class NodeSaver(Saver):
"vega-lite": ["pdf", "png", "svg", "vega"],
}

_stderr_ignore = ["WARN Can not resolve event source: window"]

@classmethod
def _stderr_filter(cls, line: str) -> bool:
return line not in cls._stderr_ignore

@classmethod
def enabled(cls) -> bool:
try:
Expand All @@ -87,15 +109,15 @@ def _serialize(self, fmt: str, content_type: str) -> MimebundleContent:
spec = self._spec

if self._mode == "vega-lite":
spec = vl2vg(spec)
spec = vl2vg(spec, self._stderr_filter)

if fmt == "vega":
return spec
elif fmt == "png":
return vg2png(spec)
return vg2png(spec, self._stderr_filter)
elif fmt == "svg":
return vg2svg(spec)
return vg2svg(spec, self._stderr_filter)
elif fmt == "pdf":
return vg2pdf(spec)
return vg2pdf(spec, self._stderr_filter)
else:
raise ValueError(f"Unrecognized format: {fmt!r}")
40 changes: 39 additions & 1 deletion altair_saver/savers/tests/test_node.py
Expand Up @@ -6,10 +6,13 @@
from PIL import Image
from PyPDF2 import PdfFileReader
import pytest
from _pytest.capture import SysCapture
from _pytest.monkeypatch import MonkeyPatch

from altair_saver import NodeSaver
from altair_saver._utils import fmt_to_mimetype
from altair_saver.savers import _node
from altair_saver.types import JSONDict


def get_testcases() -> Iterator[Tuple[str, Dict[str, Any]]]:
Expand All @@ -30,6 +33,19 @@ def get_testcases() -> Iterator[Tuple[str, Dict[str, Any]]]:
yield case, {"vega-lite": vl, "vega": vg, "svg": svg, "png": png, "pdf": pdf}


@pytest.fixture
def interactive_spec() -> JSONDict:
return {
"data": {"values": [{"x": 1, "y": 1}]},
"mark": "point",
"encoding": {
"x": {"field": "x", "type": "quantitative"},
"y": {"field": "y", "type": "quantitative"},
},
"selection": {"zoon": {"type": "interval", "bind": "scales"}},
}


def get_modes_and_formats() -> Iterator[Tuple[str, str]]:
for mode in ["vega", "vega-lite"]:
for fmt in NodeSaver.valid_formats[mode]:
Expand Down Expand Up @@ -76,7 +92,7 @@ def test_node_mimebundle_fail(name: str, data: Any) -> None:


@pytest.mark.parametrize("enabled", [True, False])
def test_enabled(monkeypatch: Any, enabled: bool) -> None:
def test_enabled(monkeypatch: MonkeyPatch, enabled: bool) -> None:
def exec_path(name: str) -> str:
if enabled:
return name
Expand All @@ -85,3 +101,25 @@ def exec_path(name: str) -> str:

monkeypatch.setattr(_node, "exec_path", exec_path)
assert NodeSaver.enabled() is enabled


@pytest.mark.parametrize("suppress_warnings", [True, False])
def test_stderr_suppression(
interactive_spec: JSONDict,
suppress_warnings: bool,
monkeypatch: MonkeyPatch,
capsys: SysCapture,
) -> None:
message = NodeSaver._stderr_ignore[0]

# Window resolve warnings are emitted by the vega CLI when an interactive chart
# is saved, and are suppressed by default.
if not suppress_warnings:
monkeypatch.setattr(NodeSaver, "_stderr_ignore", [])

NodeSaver(interactive_spec).save(fmt="png")
captured = capsys.readouterr()
if suppress_warnings:
assert message not in captured.err
else:
assert message in captured.err

0 comments on commit 5d1d895

Please sign in to comment.