From 95b49d0af96fc655601fc3d168db973e41f8b248 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 12 Mar 2025 16:00:34 -0400 Subject: [PATCH 01/14] Add argument descriptions --- src/py/kaleido/_mocker.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/src/py/kaleido/_mocker.py b/src/py/kaleido/_mocker.py index 0b158639..e216eb9f 100644 --- a/src/py/kaleido/_mocker.py +++ b/src/py/kaleido/_mocker.py @@ -111,6 +111,25 @@ def _load_figures_from_paths(paths: list[Path]): help="png (default), pdf, jpg, webp, svg, json", ) +parser.add_argument( + "--width", + type=str, + default=None, + help="width in pixels (default 700)", +) +parser.add_argument( + "--height", + type=str, + default=None, + help="height in pixels (default 500)", +) +parser.add_argument( + "--scale", + type=str, + default=None, + help="Scale ratio (default 1)", +) + parser.add_argument( "--timeout", type=int, From faf53750497134c5732553949daf7e4022db360c Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 12 Mar 2025 16:02:14 -0400 Subject: [PATCH 02/14] Populate arguments --- src/py/kaleido/_mocker.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/py/kaleido/_mocker.py b/src/py/kaleido/_mocker.py index e216eb9f..c867dae7 100644 --- a/src/py/kaleido/_mocker.py +++ b/src/py/kaleido/_mocker.py @@ -49,6 +49,11 @@ def _load_figures_from_paths(paths: list[Path]): yield { "fig": figure, "path": str(Path(args.output) / f"{path.stem}.{args.format}"), + "opts": { + "scale": args.scale, + "width": args.width, + "height": args.height, + }, } else: raise RuntimeError(f"Path {path} is not a file.") From a54a66e2eba1b2ee5f14ef01efb0585a7005084b Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 12 Mar 2025 16:14:27 -0400 Subject: [PATCH 03/14] Update uv.lock --- src/py/uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/py/uv.lock b/src/py/uv.lock index 65f686ce..7499f507 100644 --- a/src/py/uv.lock +++ b/src/py/uv.lock @@ -236,11 +236,11 @@ wheels = [ [[package]] name = "narwhals" -version = "1.29.0" +version = "1.30.0" source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/e6/f7/caa23ebc4aed3ef2314441c44e1d842e701adc6af57587ffda9263c03b6e/narwhals-1.29.0.tar.gz", hash = "sha256:1021c345d56c66ff0cc8e6d03ca8c543d01ffc411630973a5cb69ee86824d823", size = 248349 } +sdist = { url = "https://files.pythonhosted.org/packages/c4/98/be6d35e8869ab9403fa25dc3458e7af6ce36dac2873c74c7274a59b21958/narwhals-1.30.0.tar.gz", hash = "sha256:0c50cc67a5404da501302882838ec17dce51703d22cd8ad89162d6f60ea0bb19", size = 253461 } wheels = [ - { url = "https://files.pythonhosted.org/packages/ee/f6/1fcd6b3d0e21d9b75e71ae68fbc92bbb9b9b1f4f33dd81c61d8f53378b30/narwhals-1.29.0-py3-none-any.whl", hash = "sha256:653aa8e5eb435816e7b50c8def17e7e5e3324c2ffd8a3eec03fef85792e9cf5e", size = 305214 }, + { url = "https://files.pythonhosted.org/packages/e4/97/bde1e4cf1e0fe0d4c70f750b57d152c0ecb04bb35de7aa7950a5756a71d6/narwhals-1.30.0-py3-none-any.whl", hash = "sha256:443aa0a1abfae89bc65a6b888a7e310a03d1818bfb2ccd61c150199a5f954c17", size = 313611 }, ] [[package]] From 7c0d6272618da93c9eb2d32ee971a3102b755d87 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 12 Mar 2025 16:54:56 -0400 Subject: [PATCH 04/14] Set up parameterization --- src/py/kaleido/_mocker.py | 83 +++++++++++++++++++++++++++------------ 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/src/py/kaleido/_mocker.py b/src/py/kaleido/_mocker.py index c867dae7..5aa2486d 100644 --- a/src/py/kaleido/_mocker.py +++ b/src/py/kaleido/_mocker.py @@ -46,15 +46,54 @@ def _load_figures_from_paths(paths: list[Path]): with path.open(encoding="utf-8") as file: figure = orjson.loads(file.read()) _logger.info(f"Yielding {path.stem}") - yield { - "fig": figure, - "path": str(Path(args.output) / f"{path.stem}.{args.format}"), - "opts": { - "scale": args.scale, - "width": args.width, - "height": args.height, - }, - } + if args.parameterize_opts is False: + params = [ + { + "name": f"{path.stem}.{args.format or 'png'}", + "opts": { + "scale": args.scale, + "width": args.width, + "height": args.height, + }, + }, + ] + else: + widths = [args.width] if args.width else [200, 700, 1000] + heights = [args.height] if args.height else [200, 500, 1000] + scales = [args.scale] if args.scale else [0.5, 1, 2] + formats = ( + [args.format] + if args.format + else [ + "png", + "pdf", + "jpg", + "webp", + "svg", + "json", + ] + ) + params = [] + for w in widths: + for h in heights: + for s in scales: + for f in formats: + params.append( + { + "name": f"{path.stem}-{w}x{h}X{s}.{f}", + "opts": { + "scale": s, + "width": w, + "height": h, + }, + }, + ) + for p in params: + yield { + "fig": figure, + "path": str(Path(args.output) / p["name"]), + "opts": p["opts"], + } else: raise RuntimeError(f"Path {path} is not a file.") @@ -80,42 +119,36 @@ def _load_figures_from_paths(paths: list[Path]): conflict_handler="resolve", description=description, ) - parser.add_argument( "--logistro-level", default="INFO", dest="log", help="Set the logging level (default INFO)", ) - parser.add_argument( "--n", type=int, default=cpus, help="Number of tabs, defaults to # of cpus", ) - parser.add_argument( "--input", type=str, default=in_dir, - help="Directory of mock file/s, default tests/mocks", + help="Directory of mock file/s or single file (default tests/mocks)", ) - parser.add_argument( "--output", type=str, default=out_dir, - help="Directory of mock file/s, default tests/renders", + help="DIRECTORY of mock file/s (default tests/renders)", ) - parser.add_argument( "--format", type=str, - default="png", + default=None, help="png (default), pdf, jpg, webp, svg, json", ) - parser.add_argument( "--width", type=str, @@ -132,30 +165,32 @@ def _load_figures_from_paths(paths: list[Path]): "--scale", type=str, default=None, - help="Scale ratio (default 1)", + help="Scale ratio, acts as multiplier for height/width (default 1)", +) +parser.add_argument( + "--parameterize_opts", + action="store_true", + default=False, + help="Run mocks w/ different configurations.", ) - parser.add_argument( "--timeout", type=int, default=90, help="Set timeout in seconds for any 1 mock (default 60 seconds)", ) - parser.add_argument( "--headless", action="store_true", default=True, help="Set headless as True (default)", ) - parser.add_argument( "--no-headless", action="store_false", dest="headless", help="Set headless as False", ) - parser.add_argument( "--stepper", action="store_true", @@ -164,14 +199,12 @@ def _load_figures_from_paths(paths: list[Path]): help="Stepper sets n to 1, headless to False, no timeout " "and asks for confirmation before printing.", ) - parser.add_argument( "--random", type=int, default=0, help="Will select N random jsons- or if 0 (default), all.", ) - parser.add_argument( "--fail-fast", action="store_true", From 21ebeef1f3b91030fe7639f6ade5cafad61103ca Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 12 Mar 2025 17:00:18 -0400 Subject: [PATCH 05/14] Set default log level properly --- src/py/kaleido/_mocker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/py/kaleido/_mocker.py b/src/py/kaleido/_mocker.py index 5aa2486d..be10a66c 100644 --- a/src/py/kaleido/_mocker.py +++ b/src/py/kaleido/_mocker.py @@ -213,6 +213,7 @@ def _load_figures_from_paths(paths: list[Path]): ) args = parser.parse_args() +logistro.getLogger().setLevel(args.log) if not Path(args.output).is_dir(): raise ValueError(f"Specified output must be existing directory. Is {args.output!s}") From e5ae7ccfba4e49798751fd565fa7b567d7d055de Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 12 Mar 2025 17:02:32 -0400 Subject: [PATCH 06/14] Add note to changelog --- src/py/CHANGELOG.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/py/CHANGELOG.txt b/src/py/CHANGELOG.txt index 7382fe68..0793c2e8 100644 --- a/src/py/CHANGELOG.txt +++ b/src/py/CHANGELOG.txt @@ -1,3 +1,5 @@ +v1.0.0rc11 +- Write mocker tool to parameterize opts in tests v1.0.0rc10 - Allow user to pass Figure-like dicts - Fix bug by which calc fig rejected plotly figures From e838fa1d613ff90f1e93b4ac991b678b60efe6f5 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 12 Mar 2025 17:14:22 -0400 Subject: [PATCH 07/14] Set pdf to page size --- src/py/kaleido/_kaleido_tab.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/py/kaleido/_kaleido_tab.py b/src/py/kaleido/_kaleido_tab.py index f226281f..6288fa06 100644 --- a/src/py/kaleido/_kaleido_tab.py +++ b/src/py/kaleido/_kaleido_tab.py @@ -361,7 +361,7 @@ async def _img_from_response(self, response): "marginBottom": 0.1, "marginLeft": 0.1, "marginRight": 0.1, - "preferCSSPageSize": False, + "preferCSSPageSize": True, "pageRanges": "1", } pdf_response = await self.tab.send_command( From b54ab01392cd9158cda159e62d7d70b78a9c0b68 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 12 Mar 2025 17:16:02 -0400 Subject: [PATCH 08/14] Add CHANGELOG --- src/py/CHANGELOG.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/py/CHANGELOG.txt b/src/py/CHANGELOG.txt index 0793c2e8..f5f29b69 100644 --- a/src/py/CHANGELOG.txt +++ b/src/py/CHANGELOG.txt @@ -1,5 +1,6 @@ v1.0.0rc11 - Write mocker tool to parameterize opts in tests +- Crop page to pdf size v1.0.0rc10 - Allow user to pass Figure-like dicts - Fix bug by which calc fig rejected plotly figures From fb8d0aa6ba118366889fbb92a93a52b4f82ec855 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Wed, 12 Mar 2025 19:14:56 -0400 Subject: [PATCH 09/14] Add type-checks for better user errors --- src/py/CHANGELOG.txt | 1 + src/py/kaleido/_fig_tools.py | 18 +++++++++++++++++- 2 files changed, 18 insertions(+), 1 deletion(-) 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..fcddfcaf 100644 --- a/src/py/kaleido/_fig_tools.py +++ b/src/py/kaleido/_fig_tools.py @@ -56,6 +56,17 @@ def to_spec(figure, layout_opts): # Get figure layout layout = figure.get("layout", {}) + for k, v in layout_opts.items(): + match k: + case "format": + if v is not None and not isinstance(v, (str)): + raise TypeError(f"{v} must be string or None") + case "scale" | "height" | "width": + if v is not None and not isinstance(v, (float, int)): + raise TypeError(f"{v} must be numeric or None") + case _: + 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 +97,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 not isinstance(path, Path): + raise TypeError("Path supplied should be a string or `pathlib.Path` object") if path and path.suffix and not opts.get("format"): opts["format"] = path.suffix.lstrip(".") From 06031d22acd09e7da2fbdc7303e421434b26a444 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 13 Mar 2025 11:55:47 -0400 Subject: [PATCH 10/14] Remove match-case for python<3.10 --- src/py/kaleido/_fig_tools.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/py/kaleido/_fig_tools.py b/src/py/kaleido/_fig_tools.py index fcddfcaf..a86ea560 100644 --- a/src/py/kaleido/_fig_tools.py +++ b/src/py/kaleido/_fig_tools.py @@ -57,15 +57,14 @@ def to_spec(figure, layout_opts): layout = figure.get("layout", {}) for k, v in layout_opts.items(): - match k: - case "format": - if v is not None and not isinstance(v, (str)): - raise TypeError(f"{v} must be string or None") - case "scale" | "height" | "width": - if v is not None and not isinstance(v, (float, int)): - raise TypeError(f"{v} must be numeric or None") - case _: - raise AttributeError(f"Unknown key in layout options, {k}") + 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) From 48ea4e91f5d8483092f5fd783ac0dd65cd819af3 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 13 Mar 2025 12:12:39 -0400 Subject: [PATCH 11/14] Improve error verbosity --- src/py/kaleido/_fig_tools.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/py/kaleido/_fig_tools.py b/src/py/kaleido/_fig_tools.py index a86ea560..e2c0e8cd 100644 --- a/src/py/kaleido/_fig_tools.py +++ b/src/py/kaleido/_fig_tools.py @@ -14,9 +14,17 @@ def _is_figurish(o): - return hasattr(o, "to_dict") or ( + valid = hasattr(o, "to_dict") or ( isinstance(o, dict) and "data" in o and "layout" in o ) + if not valid: + _logger.error("Figure doesn't seem to be valid.") + _logger.debug( + f"Figure has to_dict? {hasattr(o, 'to_dict')} " + f"is dict? {isinstance(o, dict)} " + f"Keys: {o.keys()!s}", + ) + return valid def _get_figure_dimensions(layout, width, height): From 7d1b1a55d538889d55fd887c840fec9e03955ca5 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 13 Mar 2025 12:22:29 -0400 Subject: [PATCH 12/14] Remove layout check for is_figurish --- src/py/kaleido/_fig_tools.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/py/kaleido/_fig_tools.py b/src/py/kaleido/_fig_tools.py index e2c0e8cd..34512a00 100644 --- a/src/py/kaleido/_fig_tools.py +++ b/src/py/kaleido/_fig_tools.py @@ -14,9 +14,7 @@ def _is_figurish(o): - valid = 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.error("Figure doesn't seem to be valid.") _logger.debug( From ff37cf60940dccda6722a183b696cd036091d082 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 13 Mar 2025 12:28:35 -0400 Subject: [PATCH 13/14] Allow None-path --- src/py/kaleido/_fig_tools.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/py/kaleido/_fig_tools.py b/src/py/kaleido/_fig_tools.py index 34512a00..5970fda9 100644 --- a/src/py/kaleido/_fig_tools.py +++ b/src/py/kaleido/_fig_tools.py @@ -114,8 +114,8 @@ def build_fig_spec(fig, path, opts): # noqa: C901 if isinstance(path, str): path = Path(path) - elif not isinstance(path, Path): - raise TypeError("Path supplied should be a string or `pathlib.Path` object") + 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(".") From 2c78f5a263b41ae746217023de48eb21f1a3d622 Mon Sep 17 00:00:00 2001 From: Andrew Pikul Date: Thu, 13 Mar 2025 12:33:15 -0400 Subject: [PATCH 14/14] Fix attribute error in logging print --- src/py/kaleido/_fig_tools.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/py/kaleido/_fig_tools.py b/src/py/kaleido/_fig_tools.py index 5970fda9..84e5daa5 100644 --- a/src/py/kaleido/_fig_tools.py +++ b/src/py/kaleido/_fig_tools.py @@ -16,11 +16,10 @@ def _is_figurish(o): valid = hasattr(o, "to_dict") or (isinstance(o, dict) and "data" in o) if not valid: - _logger.error("Figure doesn't seem to be valid.") _logger.debug( f"Figure has to_dict? {hasattr(o, 'to_dict')} " f"is dict? {isinstance(o, dict)} " - f"Keys: {o.keys()!s}", + f"Keys: {o.keys() if hasattr(o, 'keys') else None!s}", ) return valid