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

Commit

Permalink
Add support for passing options to vega CLI utils via `vega_cli_optio…
Browse files Browse the repository at this point in the history
…ns` kwarg (#52)
  • Loading branch information
boydgreenfield committed Mar 28, 2020
1 parent e9e5cd6 commit fbcb7d4
Show file tree
Hide file tree
Showing 7 changed files with 90 additions and 18 deletions.
6 changes: 6 additions & 0 deletions altair_saver/_core.py
Expand Up @@ -118,6 +118,9 @@ def save(
vegaembed_version : string (optional)
For method in {"selenium", "html"}, the version of the vega-embed javascript
package to use. Default is alt.VEGAEMBED_VERSION.
vega_cli_options : list (optional)
For method="node", a list of additional arguments to pass to vega's CLI functions.
All options will be passed to all Vega commands (e.g., `vg2svg`, `vg2pdf`, etc.).
inline : boolean (optional)
For method="html", specify whether javascript sources should be included
inline rather than loaded from an external CDN. Default: False.
Expand Down Expand Up @@ -209,6 +212,9 @@ def render(
vegaembed_version : string (optional)
For method in {"selenium", "html"}, the version of the vega-embed javascript
package to use. Default is alt.VEGAEMBED_VERSION.
vega_cli_options : list (optional)
For method="node", a list of additional arguments to pass to vega's CLI functions.
All options will be passed to all Vega commands (e.g., `vg2svg`, `vg2pdf`, etc.).
inline : boolean (optional)
For method="html", specify whether javascript sources should be included
inline rather than loaded from an external CDN. Default: False.
Expand Down
2 changes: 2 additions & 0 deletions altair_saver/savers/_html.py
Expand Up @@ -127,6 +127,7 @@ def __init__(
vegaembed_version: str = alt.VEGAEMBED_VERSION,
inline: bool = False,
standalone: Optional[bool] = None,
**kwargs,
) -> None:
self._inline = inline
self._standalone = standalone
Expand All @@ -137,6 +138,7 @@ def __init__(
vega_version=vega_version,
vegalite_version=vegalite_version,
vegaembed_version=vegaembed_version,
**kwargs,
)

def _package_url(self, package: str) -> str:
Expand Down
37 changes: 23 additions & 14 deletions altair_saver/savers/_node.py
@@ -1,8 +1,7 @@
import functools
import json
import shutil
from typing import Dict, List
import warnings
from typing import Dict, List, Optional

from altair_saver.types import JSONDict, MimebundleContent
from altair_saver._utils import check_output_with_stderr
Expand Down Expand Up @@ -42,25 +41,27 @@ def vl2vg(spec: JSONDict) -> JSONDict:
return json.loads(vg_json)


def vg2png(spec: JSONDict) -> bytes:
def vg2png(spec: JSONDict, vega_cli_options: Optional[List] = 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, *(vega_cli_options or [])], input=vg_json)


def vg2pdf(spec: JSONDict) -> bytes:
def vg2pdf(spec: JSONDict, vega_cli_options: Optional[List] = 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, *(vega_cli_options or [])], input=vg_json)


def vg2svg(spec: JSONDict) -> str:
def vg2svg(spec: JSONDict, vega_cli_options: Optional[List] = 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, *(vega_cli_options or [])], input=vg_json
).decode()


class NodeSaver(Saver):
Expand All @@ -69,6 +70,17 @@ class NodeSaver(Saver):
"vega": ["pdf", "png", "svg"],
"vega-lite": ["pdf", "png", "svg", "vega"],
}
_vega_cli_options: List[str]

def __init__(
self,
spec: JSONDict,
mode: Optional[str] = None,
vega_cli_options: Optional[List] = None,
**kwargs,
) -> None:
self._vega_cli_options = vega_cli_options or []
super().__init__(spec=spec, mode=mode, **kwargs)

@classmethod
def enabled(cls) -> bool:
Expand All @@ -78,9 +90,6 @@ def enabled(cls) -> bool:
return False

def _serialize(self, fmt: str, content_type: str) -> MimebundleContent:
if self._embed_options:
warnings.warn("embed_options are not supported for method='node'.")

if self._mode not in ["vega", "vega-lite"]:
raise ValueError("mode must be either 'vega' or 'vega-lite'")

Expand All @@ -92,10 +101,10 @@ def _serialize(self, fmt: str, content_type: str) -> MimebundleContent:
if fmt == "vega":
return spec
elif fmt == "png":
return vg2png(spec)
return vg2png(spec, vega_cli_options=self._vega_cli_options)
elif fmt == "svg":
return vg2svg(spec)
return vg2svg(spec, vega_cli_options=self._vega_cli_options)
elif fmt == "pdf":
return vg2pdf(spec)
return vg2pdf(spec, vega_cli_options=self._vega_cli_options)
else:
raise ValueError(f"Unrecognized format: {fmt!r}")
1 change: 1 addition & 0 deletions altair_saver/savers/_saver.py
Expand Up @@ -37,6 +37,7 @@ def __init__(
vega_version: str = alt.VEGA_VERSION,
vegalite_version: str = alt.VEGALITE_VERSION,
vegaembed_version: str = alt.VEGAEMBED_VERSION,
**kwargs,
):
if mode is None:
mode = infer_mode_from_spec(spec)
Expand Down
2 changes: 2 additions & 0 deletions altair_saver/savers/_selenium.py
Expand Up @@ -167,6 +167,7 @@ def __init__(
webdriver: Optional[Union[str, WebDriver]] = None,
offline: bool = True,
scale_factor: Optional[float] = 1,
**kwargs,
) -> None:
self._driver_timeout = driver_timeout
self._webdriver = (
Expand All @@ -183,6 +184,7 @@ def __init__(
vega_version=vega_version,
vegalite_version=vegalite_version,
vegaembed_version=vegaembed_version,
**kwargs,
)

@classmethod
Expand Down
11 changes: 7 additions & 4 deletions altair_saver/savers/tests/test_node.py
@@ -1,7 +1,7 @@
import io
import json
import os
from typing import Any, Dict, IO, Iterator, Tuple
from typing import Any, Dict, IO, Iterator, List, Optional, Tuple

from PIL import Image
from PyPDF2 import PdfFileReader
Expand Down Expand Up @@ -37,9 +37,12 @@ def get_modes_and_formats() -> Iterator[Tuple[str, str]]:


@pytest.mark.parametrize("name,data", get_testcases())
@pytest.mark.parametrize("mode, fmt", get_modes_and_formats())
def test_node_mimebundle(name: str, data: Any, mode: str, fmt: str) -> None:
saver = NodeSaver(data[mode], mode=mode)
@pytest.mark.parametrize("mode,fmt", get_modes_and_formats())
@pytest.mark.parametrize("vega_cli_options", [None, ["--loglevel", "error"]])
def test_node_mimebundle(
name: str, data: Any, mode: str, fmt: str, vega_cli_options: Optional[List[str]]
) -> None:
saver = NodeSaver(data[mode], mode=mode, vega_cli_options=vega_cli_options)
mimetype, out = saver.mimebundle(fmt).popitem()
assert mimetype == fmt_to_mimetype(fmt)
if fmt == "png":
Expand Down
49 changes: 49 additions & 0 deletions altair_saver/tests/test_core.py
Expand Up @@ -6,6 +6,8 @@
import pandas as pd
import pytest

from _pytest.capture import SysCapture

from altair_saver import (
available_formats,
save,
Expand Down Expand Up @@ -257,6 +259,53 @@ def test_embed_options_save_html_override(spec: JSONDict) -> None:
assert f"const embedOpt = {json.dumps(embed_options)};" in html


@pytest.mark.parametrize("fmt", ["html", "svg"])
@pytest.mark.parametrize("vega_cli_options", [None, ["--loglevel", "debug"]])
def test_save_w_vega_cli_options(
monkeypatch: Any,
capsys: SysCapture,
chart: alt.TopLevelMixin,
fmt: str,
vega_cli_options: Optional[List],
) -> None:
"""Tests that `vega_cli_options` works with both NodeSaver and other Savers"""
monkeypatch.setattr(NodeSaver, "enabled", lambda: True)
monkeypatch.setattr(SeleniumSaver, "enabled", lambda: False)
fp: Union[io.BytesIO, io.StringIO]
result = save(chart, fmt=fmt, vega_cli_options=vega_cli_options)
assert result is not None
check_output(result, fmt)

stderr = capsys.readouterr().err
if vega_cli_options and fmt == "svg":
assert "DEBUG" in stderr


@pytest.mark.parametrize("vega_cli_options", [None, ["--loglevel", "debug"]])
def test_render_w_vega_cli_options(
monkeypatch: Any,
capsys: SysCapture,
chart: alt.TopLevelMixin,
vega_cli_options: Optional[List],
) -> None:
"""Tests that `vega_cli_options` works with both NodeSaver and other Savers"""
monkeypatch.setattr(NodeSaver, "enabled", lambda: True)
monkeypatch.setattr(SeleniumSaver, "enabled", lambda: False)
bundle = render(chart, fmts=["html", "svg"], vega_cli_options=vega_cli_options)
assert len(bundle) == 2
for mimetype, content in bundle.items():
assert content is not None
fmt = mimetype_to_fmt(mimetype)
if isinstance(content, dict):
check_output(json.dumps(content), fmt)
else:
check_output(content, fmt)

stderr = capsys.readouterr().err
if vega_cli_options:
assert "DEBUG" in stderr


def test_infer_format(spec: JSONDict) -> None:
with temporary_filename(suffix=".html") as filename:
with open(filename, "w") as fp:
Expand Down

0 comments on commit fbcb7d4

Please sign in to comment.