From f2fbe13ac4a549589d6b8c568efe2afb3d833288 Mon Sep 17 00:00:00 2001 From: anon Date: Fri, 8 May 2026 12:46:59 +0200 Subject: [PATCH 1/3] Accept list and ndarray for show(figsize=...) (closes #626) Validator only accepted tuple, rejecting the idiomatic figsize=[w, h] and programmatically built numpy arrays. matplotlib accepts both, so the restriction was unnecessary friction. --- src/spatialdata_plot/pl/utils.py | 11 +++++++++-- tests/pl/test_utils.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index 21246ce3..89679454 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -1,6 +1,7 @@ from __future__ import annotations import math +import numbers import os from collections import OrderedDict from collections.abc import Iterable, Mapping, Sequence @@ -2203,8 +2204,14 @@ def _validate_show_parameters( if frameon is not None and not isinstance(frameon, bool): raise TypeError("Parameter 'frameon' must be a boolean.") - if figsize is not None and not isinstance(figsize, tuple): - raise TypeError("Parameter 'figsize' must be a tuple of two floats.") + if figsize is not None: + is_seq = isinstance(figsize, tuple | list | np.ndarray) + if ( + not is_seq + or len(figsize) != 2 + or not all(isinstance(x, numbers.Real) and not isinstance(x, bool) for x in figsize) + ): + raise TypeError("Parameter 'figsize' must be a length-2 sequence of floats (tuple, list, or numpy array).") if dpi is not None and not isinstance(dpi, int): raise TypeError("Parameter 'dpi' must be an integer.") diff --git a/tests/pl/test_utils.py b/tests/pl/test_utils.py index a456d765..17164e6e 100644 --- a/tests/pl/test_utils.py +++ b/tests/pl/test_utils.py @@ -285,6 +285,38 @@ def test_utils_get_subplots_produces_correct_axs_layout(input_output): assert axs_visible == [ax.axison for ax in axs.flatten()] +@pytest.mark.parametrize( + "figsize", + [ + (6.0, 4.0), + [6.0, 4.0], + [6, 4], + np.array([6.0, 4.0]), + ], +) +def test_show_accepts_figsize_as_sequence(sdata_blobs: SpatialData, figsize): + # Regression test for #626: figsize must accept tuple, list, and ndarray. + sdata_blobs.pl.render_shapes(element="blobs_circles").pl.show(figsize=figsize) + + +@pytest.mark.parametrize( + "figsize", + [ + [6.0, 4.0, 2.0], + [6.0], + ["a", "b"], + "big", + 42, + {6, 4}, + [True, False], + ], +) +def test_show_rejects_invalid_figsize(sdata_blobs: SpatialData, figsize): + # Regression test for #626: invalid figsize values still raise TypeError. + with pytest.raises(TypeError, match="figsize"): + sdata_blobs.pl.render_shapes(element="blobs_circles").pl.show(figsize=figsize) + + class TestMultiscaleToSpatialImage: """Regression tests for #589: multiscale resolution selection.""" From e33e6a72e9514153cfc892f10cf75f1bc473999b Mon Sep 17 00:00:00 2001 From: anon Date: Fri, 8 May 2026 12:49:36 +0200 Subject: [PATCH 2/3] Simplify figsize validator and test setup - Drop `numbers.Real` dependency; use `int | float` to match the existing idiom in `_validate_show_parameters`. - Inline the sequence-type check; tighten error message. - Test `_validate_show_parameters` directly so the reject cases skip fixture setup and rendering (drops runtime from ~4s to <0.1s). --- src/spatialdata_plot/pl/utils.py | 15 +++++------- tests/pl/test_utils.py | 40 +++++++++++++++++++++++++++----- 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/src/spatialdata_plot/pl/utils.py b/src/spatialdata_plot/pl/utils.py index 89679454..97fc8242 100644 --- a/src/spatialdata_plot/pl/utils.py +++ b/src/spatialdata_plot/pl/utils.py @@ -1,7 +1,6 @@ from __future__ import annotations import math -import numbers import os from collections import OrderedDict from collections.abc import Iterable, Mapping, Sequence @@ -2204,14 +2203,12 @@ def _validate_show_parameters( if frameon is not None and not isinstance(frameon, bool): raise TypeError("Parameter 'frameon' must be a boolean.") - if figsize is not None: - is_seq = isinstance(figsize, tuple | list | np.ndarray) - if ( - not is_seq - or len(figsize) != 2 - or not all(isinstance(x, numbers.Real) and not isinstance(x, bool) for x in figsize) - ): - raise TypeError("Parameter 'figsize' must be a length-2 sequence of floats (tuple, list, or numpy array).") + if figsize is not None and ( + not isinstance(figsize, tuple | list | np.ndarray) + or len(figsize) != 2 + or not all(isinstance(x, int | float) and not isinstance(x, bool) for x in figsize) + ): + raise TypeError("Parameter 'figsize' must be a tuple, list, or numpy array of two numbers.") if dpi is not None and not isinstance(dpi, int): raise TypeError("Parameter 'dpi' must be an integer.") diff --git a/tests/pl/test_utils.py b/tests/pl/test_utils.py index 17164e6e..5a920200 100644 --- a/tests/pl/test_utils.py +++ b/tests/pl/test_utils.py @@ -285,6 +285,34 @@ def test_utils_get_subplots_produces_correct_axs_layout(input_output): assert axs_visible == [ax.axison for ax in axs.flatten()] +def _call_validator(figsize): + from spatialdata_plot.pl.utils import _validate_show_parameters + + _validate_show_parameters( + coordinate_systems=None, + legend_fontsize=None, + legend_fontweight="bold", + legend_loc=None, + legend_fontoutline=None, + na_in_legend=True, + colorbar=True, + colorbar_params=None, + wspace=None, + hspace=0.25, + ncols=4, + frameon=None, + figsize=figsize, + dpi=None, + fig=None, + title=None, + pad_extent=0, + ax=None, + return_ax=False, + save=None, + show=None, + ) + + @pytest.mark.parametrize( "figsize", [ @@ -292,11 +320,12 @@ def test_utils_get_subplots_produces_correct_axs_layout(input_output): [6.0, 4.0], [6, 4], np.array([6.0, 4.0]), + None, ], ) -def test_show_accepts_figsize_as_sequence(sdata_blobs: SpatialData, figsize): - # Regression test for #626: figsize must accept tuple, list, and ndarray. - sdata_blobs.pl.render_shapes(element="blobs_circles").pl.show(figsize=figsize) +def test_show_accepts_figsize_as_sequence(figsize): + # Regression test for #626. + _call_validator(figsize) @pytest.mark.parametrize( @@ -311,10 +340,9 @@ def test_show_accepts_figsize_as_sequence(sdata_blobs: SpatialData, figsize): [True, False], ], ) -def test_show_rejects_invalid_figsize(sdata_blobs: SpatialData, figsize): - # Regression test for #626: invalid figsize values still raise TypeError. +def test_show_rejects_invalid_figsize(figsize): with pytest.raises(TypeError, match="figsize"): - sdata_blobs.pl.render_shapes(element="blobs_circles").pl.show(figsize=figsize) + _call_validator(figsize) class TestMultiscaleToSpatialImage: From 18de92a12e2f72a468c33051b2877f2470102579 Mon Sep 17 00:00:00 2001 From: anon Date: Fri, 8 May 2026 12:50:46 +0200 Subject: [PATCH 3/3] Drop regression tests for figsize validator Per maintainer preference, omit the parametrized validator tests. --- tests/pl/test_utils.py | 60 ------------------------------------------ 1 file changed, 60 deletions(-) diff --git a/tests/pl/test_utils.py b/tests/pl/test_utils.py index 5a920200..a456d765 100644 --- a/tests/pl/test_utils.py +++ b/tests/pl/test_utils.py @@ -285,66 +285,6 @@ def test_utils_get_subplots_produces_correct_axs_layout(input_output): assert axs_visible == [ax.axison for ax in axs.flatten()] -def _call_validator(figsize): - from spatialdata_plot.pl.utils import _validate_show_parameters - - _validate_show_parameters( - coordinate_systems=None, - legend_fontsize=None, - legend_fontweight="bold", - legend_loc=None, - legend_fontoutline=None, - na_in_legend=True, - colorbar=True, - colorbar_params=None, - wspace=None, - hspace=0.25, - ncols=4, - frameon=None, - figsize=figsize, - dpi=None, - fig=None, - title=None, - pad_extent=0, - ax=None, - return_ax=False, - save=None, - show=None, - ) - - -@pytest.mark.parametrize( - "figsize", - [ - (6.0, 4.0), - [6.0, 4.0], - [6, 4], - np.array([6.0, 4.0]), - None, - ], -) -def test_show_accepts_figsize_as_sequence(figsize): - # Regression test for #626. - _call_validator(figsize) - - -@pytest.mark.parametrize( - "figsize", - [ - [6.0, 4.0, 2.0], - [6.0], - ["a", "b"], - "big", - 42, - {6, 4}, - [True, False], - ], -) -def test_show_rejects_invalid_figsize(figsize): - with pytest.raises(TypeError, match="figsize"): - _call_validator(figsize) - - class TestMultiscaleToSpatialImage: """Regression tests for #589: multiscale resolution selection."""