diff --git a/src/py/CHANGELOG.txt b/src/py/CHANGELOG.txt index f5f29b69..57874e69 100644 --- a/src/py/CHANGELOG.txt +++ b/src/py/CHANGELOG.txt @@ -1,6 +1,7 @@ v1.0.0rc11 - Write mocker tool to parameterize opts in tests - Crop page to pdf size +- Add type checks to user input for improved error messages v1.0.0rc10 - Allow user to pass Figure-like dicts - Fix bug by which calc fig rejected plotly figures diff --git a/src/py/kaleido/_fig_tools.py b/src/py/kaleido/_fig_tools.py index 49a75e57..84e5daa5 100644 --- a/src/py/kaleido/_fig_tools.py +++ b/src/py/kaleido/_fig_tools.py @@ -14,9 +14,14 @@ def _is_figurish(o): - return hasattr(o, "to_dict") or ( - isinstance(o, dict) and "data" in o and "layout" in o - ) + valid = hasattr(o, "to_dict") or (isinstance(o, dict) and "data" in o) + if not valid: + _logger.debug( + f"Figure has to_dict? {hasattr(o, 'to_dict')} " + f"is dict? {isinstance(o, dict)} " + f"Keys: {o.keys() if hasattr(o, 'keys') else None!s}", + ) + return valid def _get_figure_dimensions(layout, width, height): @@ -56,6 +61,16 @@ def to_spec(figure, layout_opts): # Get figure layout layout = figure.get("layout", {}) + for k, v in layout_opts.items(): + if k == "format": + if v is not None and not isinstance(v, (str)): + raise TypeError(f"{v} must be string or None") + elif k in ("scale", "height", "width"): + if v is not None and not isinstance(v, (float, int)): + raise TypeError(f"{v} must be numeric or None") + else: + raise AttributeError(f"Unknown key in layout options, {k}") + # Extract info extension = _get_format(layout_opts.get("format") or DEFAULT_EXT) width, height = _get_figure_dimensions( @@ -86,15 +101,20 @@ def _next_filename(path, prefix, ext): return f"{prefix}.{ext}" if n == 1 else f"{prefix}-{n}.{ext}" -def build_fig_spec(fig, path, opts): +def build_fig_spec(fig, path, opts): # noqa: C901 if not opts: opts = {} + if not _is_figurish(fig): + raise TypeError("Figure supplied doesn't seem to be a valid plotly figure.") + if hasattr(fig, "to_dict"): fig = fig.to_dict() if isinstance(path, str): path = Path(path) + elif path and not isinstance(path, Path): + raise TypeError("Path should be a string or `pathlib.Path` object (or None)") if path and path.suffix and not opts.get("format"): opts["format"] = path.suffix.lstrip(".")