Skip to content

Commit b83fa14

Browse files
authored
slightly improve error message (#2087)
1 parent 18f5e1e commit b83fa14

6 files changed

Lines changed: 61 additions & 40 deletions

File tree

skrub/_data_ops/_data_ops.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -733,9 +733,7 @@ def _repr_html_(self):
733733
graph = strip_xml_declaration(graph)
734734
has_graph = True
735735
except Exception:
736-
graph = (
737-
"Please install Pydot and GraphViz to display the computation graph."
738-
)
736+
graph = f"<p>{_utils.graphviz_error_message(html=True)}</p>"
739737
has_graph = False
740738
impl = self._skrub_impl
741739
if impl.preview_if_available() is NULL:

skrub/_data_ops/_inspection.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ def _make_full_report(
145145
overwrite=False,
146146
title=None,
147147
):
148-
_check_graphviz()
148+
_utils.check_graphviz()
149149
output_dir = _get_output_dir(output_dir, overwrite)
150150
try:
151151
# TODO dump report in callback instead of evaluating full DataOps plan
@@ -330,28 +330,10 @@ def _dot_id(n):
330330
return f"node_{n}"
331331

332332

333-
def _has_graphviz():
334-
try:
335-
import pydot
336-
337-
g = pydot.Dot()
338-
g.add_node(pydot.Node("node 0"))
339-
g.create_svg()
340-
return True
341-
except Exception:
342-
return False
343-
344-
345-
def _check_graphviz():
346-
if _has_graphviz():
347-
return
348-
raise ImportError("Please install pydot and graphviz to draw data_op graphs.")
349-
350-
351333
def draw_data_op_graph(data_op, url=None, direction="TB"):
352334
# TODO if pydot or graphviz not available fallback on some other plotting
353335
# solution eg a vendored copy of mermaid? outputting html instead of svg
354-
_check_graphviz()
336+
_utils.check_graphviz()
355337

356338
import pydot
357339

skrub/_data_ops/_utils.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,3 +125,44 @@ def rename(name):
125125
return numbered
126126

127127
return rename
128+
129+
130+
def graphviz_error_message(html=False):
131+
if html:
132+
return """\
133+
To display the DataOp graph, please install Pydot and Graphviz
134+
and make sure the dot command is in your <code>$PATH</code>.<br/>
135+
You may also need to run <code>dot -c</code> in bash or powershell
136+
to rebuild the plugin cache of Graphviz.<br/>
137+
Graphviz must be installed using your system's
138+
package manager rather than pip.<br/>
139+
<a href="https://pypi.org/project/pydot/">Pydot documentation</a><br/>
140+
<a href="https://graphviz.org/download/">Graphviz installation instructions</a><br/>
141+
"""
142+
else:
143+
return """\
144+
To display the DataOp graph,
145+
please install Pydot and Graphviz and make sure the 'dot' command is in your $PATH.
146+
You may also need to run 'dot -c' in bash or powershell
147+
to rebuild the plugin cache of Graphviz.
148+
Graphviz must be installed using your system's package manager rather than pip.
149+
https://pypi.org/project/pydot/
150+
https://graphviz.org/download/"""
151+
152+
153+
def has_graphviz():
154+
try:
155+
import pydot
156+
157+
g = pydot.Dot()
158+
g.add_node(pydot.Node("node 0"))
159+
g.create_svg()
160+
return True
161+
except Exception:
162+
return False
163+
164+
165+
def check_graphviz():
166+
if has_graphviz():
167+
return
168+
raise RuntimeError(graphviz_error_message())

skrub/_data_ops/tests/test_estimators.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
from sklearn.utils.validation import check_is_fitted
3737

3838
import skrub
39+
from skrub._data_ops import _utils
3940
from skrub._data_ops._estimator import _SharedDict
40-
from skrub._data_ops._inspection import _has_graphviz
4141

4242
#
4343
# testing utils
@@ -1157,7 +1157,7 @@ def test_plot_results(randomized_search_backend):
11571157
assert (fig is None) == (not plotly_installed)
11581158

11591159

1160-
@pytest.mark.skipif(not _has_graphviz(), reason="full report requires graphviz")
1160+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="full report requires graphviz")
11611161
def test_report(tmp_path):
11621162
data_op, data = get_data_op_and_data("simple")
11631163
pipe = data_op.skb.make_learner()

skrub/_data_ops/tests/test_inspection.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@
1111

1212
import skrub
1313
from skrub import datasets
14-
from skrub._data_ops import _inspection
14+
from skrub._data_ops import _inspection, _utils
1515

1616

17-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
17+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
1818
def test_output_dir(tmp_path):
1919
e = skrub.X()
2020
assert e.skb.full_report(open=False)["report_path"].is_relative_to(
@@ -34,7 +34,7 @@ def test_output_dir(tmp_path):
3434
)
3535

3636

37-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
37+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
3838
def test_full_report():
3939
# smoke test for the full report
4040
# TODO we should have a private function that returns the JSON data so we
@@ -60,7 +60,7 @@ def test_full_report():
6060
assert "This step did not run" in (out / "node_4.html").read_text("utf-8")
6161

6262

63-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
63+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
6464
def test_full_report_title():
6565
# TODO we should have a private function that returns the JSON data so we
6666
# can check the content before rendering with jinja
@@ -71,14 +71,14 @@ def test_full_report_title():
7171
assert title in report["report_path"].read_text("utf-8")
7272

7373

74-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
74+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
7575
def test_preview_subsample():
7676
X = datasets.fetch_employee_salaries().X
7777
preview = skrub.X(X).skb.subsample(n=3)._repr_html_()
7878
assert "subsample" in preview
7979

8080

81-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
81+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
8282
def test_full_report_failed_apply():
8383
# Somewhat contrived example for the corner case where an Apply does not
8484
# have an easily identifiable estimator.
@@ -96,7 +96,7 @@ def test_full_report_failed_apply():
9696
assert report["error"] is not None
9797

9898

99-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
99+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
100100
def test_report_fit_mode():
101101
# non-regression: in fit mode the individual node pages used to show the
102102
# dataop itself as the output instead of the result of fit_transform for
@@ -132,15 +132,15 @@ def test_report_score_mode_with_scoring():
132132
learner.report(environment={}, mode="score")
133133

134134

135-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
135+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
136136
def test_full_report_open(monkeypatch):
137137
mock = Mock()
138138
monkeypatch.setattr(webbrowser, "open", mock)
139139
skrub.as_data_op(0).skb.full_report()
140140
mock.assert_called_once()
141141

142142

143-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
143+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
144144
def test_draw_graph():
145145
g = skrub.as_data_op(0).skb.draw_graph()
146146
assert repr(g) == "<GraphDrawing: use .open() to display>"
@@ -150,7 +150,7 @@ def test_draw_graph():
150150
assert g._repr_png_().startswith(b"\x89PNG")
151151

152152

153-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
153+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
154154
def test_svg_anchor_google_colab(monkeypatch):
155155
"""non-regression test for #1589"""
156156
monkeypatch.setitem(sys.modules, "google.colab", None)
@@ -168,18 +168,18 @@ def _import(name, *args, **kwargs):
168168
return builtin_import(name, *args, **kwargs)
169169

170170
monkeypatch.setattr(builtins, "__import__", _import)
171-
with pytest.raises(ImportError, match="Please install pydot"):
171+
with pytest.raises(RuntimeError, match="please install Pydot and Graphviz"):
172172
skrub.as_data_op(0).skb.draw_graph()
173173

174174

175175
def test_no_graphviz(monkeypatch):
176176
pydot = pytest.importorskip("pydot")
177177
monkeypatch.setattr(pydot.Dot, "create_svg", Mock(side_effect=Exception()))
178-
with pytest.raises(ImportError, match="Please install pydot and graphviz"):
178+
with pytest.raises(RuntimeError, match="please install Pydot and Graphviz"):
179179
skrub.as_data_op(0).skb.draw_graph()
180180

181181

182-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
182+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
183183
def test_draw_graph_open(monkeypatch):
184184
mock = Mock()
185185
monkeypatch.setattr(_inspection, "open_in_browser", mock)

skrub/tests/test_data_ops_stack_description.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from sklearn.preprocessing import FunctionTransformer
66

77
import skrub
8-
from skrub._data_ops import _inspection
8+
from skrub._data_ops import _utils
99

1010
# Those tests have to be outside of the `_data_ops` module (and thus out of
1111
# _data_ops/tests/, as we place the tests inside the package), because to
@@ -81,7 +81,7 @@ def test_apply_eval_failure(eval_data_op):
8181
eval_data_op(e, {"a": 1.0, "b": 2.0})
8282

8383

84-
@pytest.mark.skipif(not _inspection._has_graphviz(), reason="report requires graphviz")
84+
@pytest.mark.skipif(not _utils.has_graphviz(), reason="report requires graphviz")
8585
def test_escaping_characters():
8686
# fmt: off
8787
out = skrub.as_data_op("") + "\n" + '"' + "\t" +'\\ ' \

0 commit comments

Comments
 (0)