From 8528793a727528ded2ac568839b63a8b4cb44ef3 Mon Sep 17 00:00:00 2001 From: Matt Craig Date: Mon, 30 Jun 2025 10:51:25 -0700 Subject: [PATCH 1/6] Add kwargs everywhere and a test that kwargs can be supplied --- src/astro_image_display_api/api_test.py | 94 +++++++++++++++++++ .../image_viewer_logic.py | 66 +++++++++++-- .../interface_definition.py | 75 ++++++++++++--- 3 files changed, 214 insertions(+), 21 deletions(-) diff --git a/src/astro_image_display_api/api_test.py b/src/astro_image_display_api/api_test.py index be70d69..e5c1ff7 100644 --- a/src/astro_image_display_api/api_test.py +++ b/src/astro_image_display_api/api_test.py @@ -822,3 +822,97 @@ def test_get_image(self, data): with pytest.raises(ValueError, match="[Ii]mage label.*not found"): self.image.get_image(image_label="not a valid label") + + def test_all_methods_accept_additional_kwargs(self, data, catalog): + """ + Make sure all methods accept additional keyword arguments + that are not defined in the protocol. + """ + from astro_image_display_api import ImageViewerInterface + + all_methods_and_attributes = ImageViewerInterface.__protocol_attrs__ + + all_methods = [ + method + for method in all_methods_and_attributes + if callable(getattr(self.image, method)) + ] + + # Make a small dictionary keys that are random characters + additional_kwargs = {k: f"value{k}" for k in ["fsda", "urioeh", "m898h]"]} + + # Make a dictionary of the required arguments for any methods that have required + # argument + required_args = dict( + load_image=data, + set_cuts=(10, 100), + set_stretch=LogStretch(), + set_colormap="viridis", + save="test.png", + load_catalog=catalog, + ) + + failed_methods = [] + + # Take out the loading methods because they must happen first and take out + # remove_catalog because it must happen last. + all_methods = list( + set(all_methods) - set(["load_image", "load_catalog", "remove_catalog"]) + ) + + # Load an image and a catalog first since other methods require these + # have been done + try: + self.image.load_image(required_args["load_image"], **additional_kwargs) + except TypeError as e: + if "required positional argument" not in str(e): + # If the error is not about a missing required argument, we + # consider it a failure. + failed_methods.append("load_image") + else: + raise e + + try: + self.image.load_catalog(required_args["load_catalog"], **additional_kwargs) + except TypeError as e: + if "required positional argument" not in str(e): + # If the error is not about a missing required argument, we + # consider it a failure. + failed_methods.append("load_catalog") + else: + raise e + + print("\n".join(all_methods)) + + if not failed_methods: + # No point in running some of these if setting image or catalog has failed + for method in all_methods + ["remove_catalog"]: + # Call each method with the required arguments and additional kwargs + # Accumulate the failures and report them at the end + try: + if method in required_args: + # If the method has required arguments, call it with those + getattr(self.image, method)( + required_args[method], **additional_kwargs + ) + else: + # If the method does not have required arguments, just call it + # with additional kwargs + getattr(self.image, method)(**additional_kwargs) + except TypeError as e: + if "required positional argument" not in str(e): + # If the error is not about a missing required argument, we + # consider it a failure. + failed_methods.append(method) + else: + raise e + + else: + failed_methods.append( + "No other methods were tests because the ones above failed." + ) + + assert not failed_methods, ( + "The following methods failed when called with additional kwargs:\n\t" + f"{'\n\t'.join(failed_methods)}" + ) diff --git a/src/astro_image_display_api/image_viewer_logic.py b/src/astro_image_display_api/image_viewer_logic.py index fca90a6..b97a960 100644 --- a/src/astro_image_display_api/image_viewer_logic.py +++ b/src/astro_image_display_api/image_viewer_logic.py @@ -129,7 +129,11 @@ def _default_catalog_style(self) -> dict[str, Any]: "size": 5, } - def get_stretch(self, image_label: str | None = None) -> BaseStretch: + def get_stretch( + self, + image_label: str | None = None, + **kwargs, # noqa: ARG002 + ) -> BaseStretch: image_label = self._resolve_image_label(image_label) if image_label not in self._images: raise ValueError( @@ -137,7 +141,12 @@ def get_stretch(self, image_label: str | None = None) -> BaseStretch: ) return self._images[image_label].stretch - def set_stretch(self, value: BaseStretch, image_label: str | None = None) -> None: + def set_stretch( + self, + value: BaseStretch, + image_label: str | None = None, + **kwargs, # noqa: ARG002 + ) -> None: if not isinstance(value, BaseStretch): raise TypeError( f"Stretch option {value} is not valid. Must be an " @@ -150,7 +159,11 @@ def set_stretch(self, value: BaseStretch, image_label: str | None = None) -> Non ) self._images[image_label].stretch = value - def get_cuts(self, image_label: str | None = None) -> tuple: + def get_cuts( + self, + image_label: str | None = None, + **kwargs, # noqa: ARG002 + ) -> tuple: image_label = self._resolve_image_label(image_label) if image_label not in self._images: raise ValueError( @@ -162,6 +175,7 @@ def set_cuts( self, value: tuple[numbers.Real, numbers.Real] | BaseInterval, image_label: str | None = None, + **kwargs, # noqa: ARG002 ) -> None: if isinstance(value, tuple) and len(value) == 2: self._cuts = ManualInterval(value[0], value[1]) @@ -179,7 +193,12 @@ def set_cuts( ) self._images[image_label].cuts = self._cuts - def set_colormap(self, map_name: str, image_label: str | None = None) -> None: + def set_colormap( + self, + map_name: str, + image_label: str | None = None, + **kwargs, # noqa: ARG002 + ) -> None: image_label = self._resolve_image_label(image_label) if image_label not in self._images: raise ValueError( @@ -189,7 +208,11 @@ def set_colormap(self, map_name: str, image_label: str | None = None) -> None: set_colormap.__doc__ = ImageViewerInterface.set_colormap.__doc__ - def get_colormap(self, image_label: str | None = None) -> str: + def get_colormap( + self, + image_label: str | None = None, + **kwargs, # noqa: ARG002 + ) -> str: image_label = self._resolve_image_label(image_label) if image_label not in self._images: raise ValueError( @@ -201,7 +224,11 @@ def get_colormap(self, image_label: str | None = None) -> str: # The methods, grouped loosely by purpose - def get_catalog_style(self, catalog_label=None) -> dict[str, Any]: + def get_catalog_style( + self, + catalog_label=None, + **kwargs, # noqa: ARG002 + ) -> dict[str, Any]: """ Get the style for the catalog. @@ -295,6 +322,7 @@ def load_image( self, file: str | os.PathLike | ArrayLike | NDData, image_label: str | None = None, + **kwargs, # noqa: ARG002 ) -> None: """ Load a FITS file into the viewer. @@ -458,7 +486,12 @@ def _load_asdf(self, asdf_file: str | os.PathLike, image_label: str | None) -> N ) # Saving contents of the view and accessing the view - def save(self, filename: str | os.PathLike, overwrite: bool = False) -> None: + def save( + self, + filename: str | os.PathLike, + overwrite: bool = False, + **kwargs, # noqa: ARG002 + ) -> None: """ Save the current view to a file. @@ -490,6 +523,7 @@ def load_catalog( use_skycoord: bool = False, catalog_label: str | None = None, catalog_style: dict | None = None, + **kwargs, # noqa: ARG002 ) -> None: try: coords = table[skycoord_colname] @@ -549,7 +583,11 @@ def load_catalog( load_catalog.__doc__ = ImageViewerInterface.load_catalog.__doc__ - def remove_catalog(self, catalog_label: str | None = None) -> None: + def remove_catalog( + self, + catalog_label: str | None = None, + **kwargs, # noqa: ARG002 + ) -> None: """ Remove markers from the image. @@ -584,6 +622,7 @@ def get_catalog( y_colname: str = "y", skycoord_colname: str = "coord", catalog_label: str | None = None, + **kwargs, # noqa: ARG002 ) -> Table: # Dostring is copied from the interface definition, so it is not # duplicated here. @@ -603,7 +642,10 @@ def get_catalog( get_catalog.__doc__ = ImageViewerInterface.get_catalog.__doc__ - def get_catalog_names(self) -> list[str]: + def get_catalog_names( + self, + **kwargs, # noqa: ARG002 + ) -> list[str]: return list(self._user_catalog_labels()) get_catalog_names.__doc__ = ImageViewerInterface.get_catalog_names.__doc__ @@ -614,6 +656,7 @@ def set_viewport( center: SkyCoord | tuple[numbers.Real, numbers.Real] | None = None, fov: Quantity | numbers.Real | None = None, image_label: str | None = None, + **kwargs, # noqa: ARG002 ) -> None: image_label = self._resolve_image_label(image_label) @@ -685,7 +728,10 @@ def set_viewport( set_viewport.__doc__ = ImageViewerInterface.set_viewport.__doc__ def get_viewport( - self, sky_or_pixel: str | None = None, image_label: str | None = None + self, + sky_or_pixel: str | None = None, + image_label: str | None = None, + **kwargs, # noqa: ARG002 ) -> dict[str, Any]: if sky_or_pixel not in (None, "sky", "pixel"): raise ValueError("sky_or_pixel must be 'sky', 'pixel', or None.") diff --git a/src/astro_image_display_api/interface_definition.py b/src/astro_image_display_api/interface_definition.py index 0315a3d..0dbf287 100644 --- a/src/astro_image_display_api/interface_definition.py +++ b/src/astro_image_display_api/interface_definition.py @@ -25,7 +25,7 @@ class ImageViewerInterface(Protocol): # Method for loading image data @abstractmethod - def load_image(self, data: Any, image_label: str | None = None) -> None: + def load_image(self, data: Any, image_label: str | None = None, **kwargs) -> None: """ Load data into the viewer. At a minimum, this should allow a FITS file to be loaded. Viewers may allow additional data types to be loaded, such as @@ -40,6 +40,9 @@ def load_image(self, data: Any, image_label: str | None = None) -> None: image_label : optional The label for the image. + **kwargs + Additional keyword arguments that may be used by the viewer. + Notes ----- @@ -97,6 +100,7 @@ def set_cuts( self, cuts: tuple[numbers.Real, numbers.Real] | BaseInterval, image_label: str | None = None, + **kwargs, ) -> None: """ Set the cuts for the image. @@ -112,6 +116,9 @@ def set_cuts( The label of the image to set the cuts for. If not given and there is only one image loaded, the cuts for that image are set. + **kwargs + Additional keyword arguments that may be used by the viewer. + Raises ------ TypeError @@ -130,7 +137,7 @@ def set_cuts( raise NotImplementedError @abstractmethod - def get_cuts(self, image_label: str | None = None) -> BaseInterval: + def get_cuts(self, image_label: str | None = None, **kwargs) -> BaseInterval: """ Get the current cuts for the image. @@ -146,6 +153,9 @@ def get_cuts(self, image_label: str | None = None) -> BaseInterval: cuts : `astropy.visualization.BaseInterval` The Astropy interval object representing the current cuts. + **kwargs + Additional keyword arguments that may be used by the viewer. + Raises ------ ValueError @@ -159,7 +169,9 @@ def get_cuts(self, image_label: str | None = None) -> BaseInterval: raise NotImplementedError @abstractmethod - def set_stretch(self, stretch: BaseStretch, image_label: str | None = None) -> None: + def set_stretch( + self, stretch: BaseStretch, image_label: str | None = None, **kwargs + ) -> None: """ Set the stretch for the image. @@ -169,10 +181,13 @@ def set_stretch(self, stretch: BaseStretch, image_label: str | None = None) -> N The stretch to set. This can be any subclass of `~astropy.visualization.BaseStretch`. - image_label : str, optional + image_label : The label of the image to set the stretch for. If not given and there is only one image loaded, the stretch for that image are set. + **kwargs + Additional keyword arguments that may be used by the viewer. + Raises ------ TypeError @@ -191,7 +206,7 @@ def set_stretch(self, stretch: BaseStretch, image_label: str | None = None) -> N raise NotImplementedError @abstractmethod - def get_stretch(self, image_label: str | None = None) -> BaseStretch: + def get_stretch(self, image_label: str | None = None, **kwargs) -> BaseStretch: """ Get the current stretch for the image. @@ -202,6 +217,9 @@ def get_stretch(self, image_label: str | None = None) -> BaseStretch: only one image loaded, the cuts for that image are returned. If there are multiple images and no label is provided, an error is raised. + **kwargs + Additional keyword arguments that may be used by the viewer. + Returns ------- stretch : `~astropy.visualization.BaseStretch` @@ -214,7 +232,9 @@ def get_stretch(self, image_label: str | None = None) -> BaseStretch: raise NotImplementedError @abstractmethod - def set_colormap(self, map_name: str, image_label: str | None = None) -> None: + def set_colormap( + self, map_name: str, image_label: str | None = None, **kwargs + ) -> None: """ Set the colormap for the image specified by image_label. @@ -230,6 +250,9 @@ def set_colormap(self, map_name: str, image_label: str | None = None) -> None: only one image loaded, the colormap for that image is set. If there are multiple images and no label is provided, an error is raised. + **kwargs + Additional keyword arguments that may be used by the viewer. + Raises ------ ValueError @@ -245,7 +268,7 @@ def set_colormap(self, map_name: str, image_label: str | None = None) -> None: raise NotImplementedError @abstractmethod - def get_colormap(self, image_label: str | None = None) -> str: + def get_colormap(self, image_label: str | None = None, **kwargs) -> str: """ Get the current colormap for the image. @@ -256,6 +279,9 @@ def get_colormap(self, image_label: str | None = None) -> str: only one image loaded, the colormap for that image is returned. If there are multiple images and no label is provided, an error is raised. + **kwargs + Additional keyword arguments that may be used by the viewer. + Returns ------- map_name : str @@ -275,7 +301,9 @@ def get_colormap(self, image_label: str | None = None) -> str: # Saving contents of the view and accessing the view @abstractmethod - def save(self, filename: str | os.PathLike, overwrite: bool = False) -> None: + def save( + self, filename: str | os.PathLike, overwrite: bool = False, **kwargs + ) -> None: """ Save the current view to a file. @@ -289,6 +317,9 @@ def save(self, filename: str | os.PathLike, overwrite: bool = False) -> None: If `True`, overwrite the file if it exists. Default is `False`. + **kwargs + Additional keyword arguments that may be used by the viewer. + Raises ------ FileExistsError @@ -310,6 +341,7 @@ def load_catalog( use_skycoord: bool = False, catalog_label: str | None = None, catalog_style: dict | None = None, + **kwargs, ) -> None: """ Add catalog entries to the viewer at positions given by the catalog. @@ -339,6 +371,8 @@ def load_catalog( represent the catalog. See `~astro_image_display_api.interface_definition.ImageViewerInterface.set_catalog_style` for details. + **kwargs + Additional keyword arguments that may be used by the viewer. Raises ------ @@ -398,7 +432,7 @@ def set_catalog_style( raise NotImplementedError @abstractmethod - def get_catalog_style(self, catalog_label: str | None = None) -> dict: + def get_catalog_style(self, catalog_label: str | None = None, **kwargs) -> dict: """ Get the style of the catalog markers. @@ -411,6 +445,9 @@ def get_catalog_style(self, catalog_label: str | None = None) -> dict: is raised. If the label does not correspond to a loaded catalog, an empty dictionary is returned. + **kwargs + Additional keyword arguments that may be used by the viewer. + Returns ------- dict @@ -431,7 +468,7 @@ def get_catalog_style(self, catalog_label: str | None = None) -> dict: raise NotImplementedError @abstractmethod - def remove_catalog(self, catalog_label: str | None = None) -> None: + def remove_catalog(self, catalog_label: str | None = None, **kwargs) -> None: """ Remove markers from the image. @@ -442,6 +479,9 @@ def remove_catalog(self, catalog_label: str | None = None) -> None: remove all catalogs. If not given and there is only one catalog loaded, that catalog is removed. + **kwargs + Additional keyword arguments that may be used by the viewer. + Raises ------ ValueError @@ -462,6 +502,7 @@ def get_catalog( y_colname: str = "y", skycoord_colname: str = "coord", catalog_label: str | None = None, + **kwargs, ) -> Table: """ Get the marker positions. @@ -480,6 +521,9 @@ def get_catalog( catalog_label : str, optional The name of the catalog set to get. + **kwargs + Additional keyword arguments that may be used by the viewer. + Returns ------- table : `astropy.table.Table` @@ -521,6 +565,7 @@ def set_viewport( center: SkyCoord | tuple[float, float] | None = None, fov: Quantity | float | None = None, image_label: str | None = None, + **kwargs: Any, ) -> None: """ Set the viewport of the image, which defines the center and field of view. @@ -540,6 +585,9 @@ def set_viewport( only one image loaded, the viewport for that image is set. If there are multiple images and no label is provided, an error is raised. + **kwargs + Additional keyword arguments that may be used by the viewer. + Raises ------ TypeError @@ -564,7 +612,10 @@ def set_viewport( @abstractmethod def get_viewport( - self, sky_or_pixel: str | None = None, image_label: str | None = None + self, + sky_or_pixel: str | None = None, + image_label: str | None = None, + **kwargs: Any, ) -> dict[str, Any]: """ Get the current viewport of the image. @@ -583,6 +634,8 @@ def get_viewport( The label of the image to get the viewport for. If not given and there is only one image loaded, the viewport for that image is returned. If there are multiple images and no label is provided, an error is raised. + **kwargs + Additional keyword arguments that may be used by the viewer. Returns ------- From fa4b690c6cd3d029ccf16c4e3ecd48e6632809f6 Mon Sep 17 00:00:00 2001 From: Matt Craig Date: Mon, 7 Jul 2025 17:10:09 -0700 Subject: [PATCH 2/6] Run test that all methods accept kwargs in temporary dir --- src/astro_image_display_api/api_test.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/astro_image_display_api/api_test.py b/src/astro_image_display_api/api_test.py index e5c1ff7..6fc0dcb 100644 --- a/src/astro_image_display_api/api_test.py +++ b/src/astro_image_display_api/api_test.py @@ -1,4 +1,5 @@ import numbers +import os import numpy as np import pytest @@ -823,11 +824,13 @@ def test_get_image(self, data): with pytest.raises(ValueError, match="[Ii]mage label.*not found"): self.image.get_image(image_label="not a valid label") - def test_all_methods_accept_additional_kwargs(self, data, catalog): + def test_all_methods_accept_additional_kwargs(self, data, catalog, tmp_path): """ Make sure all methods accept additional keyword arguments that are not defined in the protocol. """ + # Run in a temperature directory since we are saving an image + os.chdir(tmp_path) from astro_image_display_api import ImageViewerInterface all_methods_and_attributes = ImageViewerInterface.__protocol_attrs__ @@ -885,7 +888,9 @@ def test_all_methods_accept_additional_kwargs(self, data, catalog): print("\n".join(all_methods)) if not failed_methods: - # No point in running some of these if setting image or catalog has failed + # No point in running some of these if setting image or catalog has failed. + # Run remove_catalog last so that it does not interfere with the + # other methods that require an image or catalog to be loaded. for method in all_methods + ["remove_catalog"]: # Call each method with the required arguments and additional kwargs # Accumulate the failures and report them at the end From 3eb7703b4429c87116ae7c39f6edf3aff9dc3e8b Mon Sep 17 00:00:00 2001 From: Matt Craig Date: Mon, 7 Jul 2025 17:10:54 -0700 Subject: [PATCH 3/6] Add kwargs to a couple more methods --- src/astro_image_display_api/image_viewer_logic.py | 9 +++++++-- src/astro_image_display_api/interface_definition.py | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/astro_image_display_api/image_viewer_logic.py b/src/astro_image_display_api/image_viewer_logic.py index b97a960..6879337 100644 --- a/src/astro_image_display_api/image_viewer_logic.py +++ b/src/astro_image_display_api/image_viewer_logic.py @@ -361,7 +361,9 @@ def load_image( # working with the new image. self._wcs = self._images[image_label].wcs - def get_image(self, image_label: str | None = None): + def get_image( + self, image_label: str | None = None, **kwargs # noqa: ARG002 + ) -> ArrayLike | NDData | CCDData: image_label = self._resolve_image_label(image_label) if image_label not in self._images: raise ValueError( @@ -369,7 +371,10 @@ def get_image(self, image_label: str | None = None): ) return self._images[image_label].data - def get_image_labels(self): + def get_image_labels( + self, + **kwargs, # noqa: ARG002 + ) -> tuple[str, ...]: return tuple(self._images.keys()) def _determine_largest_dimension(self, shape: tuple[int, int]) -> int: diff --git a/src/astro_image_display_api/interface_definition.py b/src/astro_image_display_api/interface_definition.py index 0dbf287..aafb379 100644 --- a/src/astro_image_display_api/interface_definition.py +++ b/src/astro_image_display_api/interface_definition.py @@ -55,6 +55,7 @@ def load_image(self, data: Any, image_label: str | None = None, **kwargs) -> Non def get_image( self, image_label: str | None = None, + **kwargs, ) -> Any: """ Parameters @@ -63,6 +64,9 @@ def get_image( The label of the image to set the cuts for. If not given and there is only one image loaded, that image is returned. + **kwargs + Additional keyword arguments that may be used by the viewer. + Returns ------- image_data : Any @@ -84,6 +88,7 @@ def get_image( @abstractmethod def get_image_labels( self, + **kwargs, ) -> tuple[str]: """ Get the labels of the loaded images. From d36b6f3b81f4cce1cada587a3762b25eb628b5d6 Mon Sep 17 00:00:00 2001 From: Matt Craig Date: Mon, 7 Jul 2025 17:28:33 -0700 Subject: [PATCH 4/6] Fix a docstring --- src/astro_image_display_api/interface_definition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/astro_image_display_api/interface_definition.py b/src/astro_image_display_api/interface_definition.py index aafb379..5993f78 100644 --- a/src/astro_image_display_api/interface_definition.py +++ b/src/astro_image_display_api/interface_definition.py @@ -158,7 +158,7 @@ def get_cuts(self, image_label: str | None = None, **kwargs) -> BaseInterval: cuts : `astropy.visualization.BaseInterval` The Astropy interval object representing the current cuts. - **kwargs + kwargs : Additional keyword arguments that may be used by the viewer. Raises From e32c50b29414cd0d072f9c59a960df7a464165c5 Mon Sep 17 00:00:00 2001 From: Matt Craig Date: Mon, 7 Jul 2025 20:56:18 -0700 Subject: [PATCH 5/6] Save file to temporary path instead of changing directory --- src/astro_image_display_api/api_test.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/astro_image_display_api/api_test.py b/src/astro_image_display_api/api_test.py index 6fc0dcb..a026623 100644 --- a/src/astro_image_display_api/api_test.py +++ b/src/astro_image_display_api/api_test.py @@ -1,5 +1,4 @@ import numbers -import os import numpy as np import pytest @@ -829,8 +828,6 @@ def test_all_methods_accept_additional_kwargs(self, data, catalog, tmp_path): Make sure all methods accept additional keyword arguments that are not defined in the protocol. """ - # Run in a temperature directory since we are saving an image - os.chdir(tmp_path) from astro_image_display_api import ImageViewerInterface all_methods_and_attributes = ImageViewerInterface.__protocol_attrs__ @@ -851,7 +848,7 @@ def test_all_methods_accept_additional_kwargs(self, data, catalog, tmp_path): set_cuts=(10, 100), set_stretch=LogStretch(), set_colormap="viridis", - save="test.png", + save=tmp_path / "test.png", load_catalog=catalog, ) From c43c16b64812aea59f1707f8fc0e28742c601e7f Mon Sep 17 00:00:00 2001 From: Matt Craig Date: Mon, 7 Jul 2025 21:09:01 -0700 Subject: [PATCH 6/6] Address copilot suggestions --- src/astro_image_display_api/api_test.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/astro_image_display_api/api_test.py b/src/astro_image_display_api/api_test.py index a026623..8061fdf 100644 --- a/src/astro_image_display_api/api_test.py +++ b/src/astro_image_display_api/api_test.py @@ -882,8 +882,6 @@ def test_all_methods_accept_additional_kwargs(self, data, catalog, tmp_path): else: raise e - print("\n".join(all_methods)) - if not failed_methods: # No point in running some of these if setting image or catalog has failed. # Run remove_catalog last so that it does not interfere with the @@ -911,7 +909,7 @@ def test_all_methods_accept_additional_kwargs(self, data, catalog, tmp_path): else: failed_methods.append( - "No other methods were tests because the ones above failed." + "No other methods were tested because the ones above failed." ) assert not failed_methods, (