From 105ef9033dc5b0c0ad8e855f4c4011bff0635dd4 Mon Sep 17 00:00:00 2001 From: pragnyanramtha Date: Wed, 13 May 2026 23:25:54 +0000 Subject: [PATCH 1/2] Replace runtime validation asserts --- mllam_data_prep/config.py | 3 ++- mllam_data_prep/ops/cropping.py | 25 +++++++++++++++++-------- mllam_data_prep/ops/selection.py | 14 ++++++++++---- tests/test_config.py | 15 +++++++++++++++ tests/test_convex_hull_cropping.py | 16 ++++++++++++++++ tests/test_selection.py | 19 ++++++++++++++----- 6 files changed, 74 insertions(+), 18 deletions(-) diff --git a/mllam_data_prep/config.py b/mllam_data_prep/config.py index b7a0ab4..bfa1253 100644 --- a/mllam_data_prep/config.py +++ b/mllam_data_prep/config.py @@ -491,7 +491,8 @@ def find_config_differences( ) args = argparser.parse_args() - assert args.f.endswith(".yaml"), "Config file must have a .yaml extension." + if not args.f.endswith(".yaml"): + raise ValueError("Config file must have a .yaml extension.") config = Config.from_yaml_file(args.f) import rich diff --git a/mllam_data_prep/ops/cropping.py b/mllam_data_prep/ops/cropping.py index 213215d..d1a8c4e 100644 --- a/mllam_data_prep/ops/cropping.py +++ b/mllam_data_prep/ops/cropping.py @@ -53,8 +53,17 @@ def create_convex_hull_mask(ds: xr.Dataset, ds_reference: xr.Dataset) -> xr.Data da_lon, da_lat = _get_latlon_coords(ds) da_lon_ref, da_lat_ref = _get_latlon_coords(ds_reference) - assert da_lat.dims == da_lon.dims - assert da_lat_ref.dims == da_lon_ref.dims + if da_lat.dims != da_lon.dims: + raise ValueError( + "Latitude and longitude coordinates in ds must have matching " + f"dimensions, got {da_lat.dims!r} and {da_lon.dims!r}." + ) + if da_lat_ref.dims != da_lon_ref.dims: + raise ValueError( + "Latitude and longitude coordinates in ds_reference must have " + f"matching dimensions, got {da_lat_ref.dims!r} and " + f"{da_lon_ref.dims!r}." + ) # latlon to (x, y, z) on unit sphere da_ref_xyz = _latlon_to_unit_sphere_xyz(da_lat=da_lat_ref, da_lon=da_lon_ref) @@ -65,9 +74,9 @@ def create_convex_hull_mask(ds: xr.Dataset, ds_reference: xr.Dataset) -> xr.Data da_interior_mask = xr.apply_ufunc( chull_lam.contains_lonlat, da_lon.load(), da_lat.load(), vectorize=True ).astype(bool) - da_interior_mask.attrs[ - "long_name" - ] = "contained in convex hull of source dataset (da_ref)" + da_interior_mask.attrs["long_name"] = ( + "contained in convex hull of source dataset (da_ref)" + ) # Get points at edge of convex hull chull_lam_lon, chull_lam_lat = list(chull_lam.to_lonlat())[0] @@ -254,9 +263,9 @@ def distance_to_convex_hull_boundary( da_mindist_to_ref = xr.DataArray( mindist_to_ref, coords=ds_exterior_lat.coords, dims=ds_exterior_lat.dims ) - da_mindist_to_ref.attrs[ - "long_name" - ] = "minimum distance to convex hull boundary of reference dataset" + da_mindist_to_ref.attrs["long_name"] = ( + "minimum distance to convex hull boundary of reference dataset" + ) da_mindist_to_ref.attrs["units"] = "radians" if include_convex_hull_mask: diff --git a/mllam_data_prep/ops/selection.py b/mllam_data_prep/ops/selection.py index 37b91c1..9682e73 100644 --- a/mllam_data_prep/ops/selection.py +++ b/mllam_data_prep/ops/selection.py @@ -67,7 +67,11 @@ def select_by_kwargs(ds, **coord_ranges): sel_end = _normalize_slice_startstop(selection.end) sel_step = _normalize_slice_step(selection.step) - assert sel_start != sel_end, "Start and end cannot be the same" + if sel_start == sel_end: + raise ValueError( + f"Start and end cannot be the same for coordinate {coord!r}: " + f"{sel_start!r}" + ) # we don't select with the step size for now, but simply check (below) that # the step size in the data is the same as the requested step size @@ -79,9 +83,11 @@ def select_by_kwargs(ds, **coord_ranges): if sel_step is not None: check_step(sel_step, coord, ds) - assert ( - len(ds[coord]) > 0 - ), f"You have selected an empty range {sel_start}:{sel_end} for coordinate {coord}" + if len(ds[coord]) == 0: + raise ValueError( + f"You have selected an empty range {sel_start}:{sel_end} " + f"for coordinate {coord}" + ) elif isinstance(selection, list): ds = ds.sel({coord: selection}) diff --git a/tests/test_config.py b/tests/test_config.py index 5f7896a..13a61fe 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,3 +1,6 @@ +import subprocess +import sys + import pytest from dataclass_wizard.errors import MissingFields, UnknownJSONKey @@ -129,3 +132,15 @@ def test_config_roundtrip(): assert original_config == roundtrip_config_dict assert original_config == roundtrip_config_yaml assert original_config == roundtrip_config_json + + +def test_config_main_rejects_non_yaml_path(): + result = subprocess.run( + [sys.executable, "-O", "-m", "mllam_data_prep.config", "-f", "config.json"], + capture_output=True, + check=False, + text=True, + ) + + assert result.returncode != 0 + assert ".yaml extension" in result.stderr diff --git a/tests/test_convex_hull_cropping.py b/tests/test_convex_hull_cropping.py index d9d3042..fb1ae01 100644 --- a/tests/test_convex_hull_cropping.py +++ b/tests/test_convex_hull_cropping.py @@ -4,6 +4,7 @@ import numpy as np import pytest +import xarray as xr import mllam_data_prep as mdp import mllam_data_prep.config as mdp_config @@ -86,6 +87,21 @@ def test_create_convex_hull_mask(): assert n_points_margin_region < n_outside +@pytest.mark.parametrize("bad_dataset", ["ds", "ds_reference"]) +def test_create_convex_hull_mask_requires_matching_lat_lon_dims(bad_dataset): + valid_dataset = xr.Dataset( + coords={"lat": ("x", [0.0, 1.0]), "lon": ("x", [0.0, 1.0])} + ) + invalid_dataset = xr.Dataset( + coords={"lat": ("x", [0.0, 1.0]), "lon": ("y", [0.0, 1.0])} + ) + ds = invalid_dataset if bad_dataset == "ds" else valid_dataset + ds_reference = invalid_dataset if bad_dataset == "ds_reference" else valid_dataset + + with pytest.raises(ValueError, match=bad_dataset): + cropping.create_convex_hull_mask(ds=ds, ds_reference=ds_reference) + + @pytest.mark.parametrize("include_interior_points", [True, False]) def test_create_cropped_dataset(include_interior_points): diff --git a/tests/test_selection.py b/tests/test_selection.py index 044b66e..d7f51db 100644 --- a/tests/test_selection.py +++ b/tests/test_selection.py @@ -35,17 +35,26 @@ def test_range_slice_within_range(ds): ds -@pytest.mark.parametrize("x_start, x_end", ([-50000, -51000], [0, 500000])) -def test_error_on_empty_range(ds, x_start, x_end): +@pytest.mark.parametrize("x_start, x_end", ([-2, -1], [10, 11])) +def test_error_on_empty_range(x_start, x_end): """ Test if an error is thrown if the chosen range is empty """ - y_start = -600000 - y_end = -590000 + ds = xr.Dataset(coords={"x": [0, 1, 2], "y": [0, 1, 2]}) + y_start = 0 + y_end = 2 coord_ranges = { "x": mdp.config.Range(start=x_start, end=x_end), "y": mdp.config.Range(start=y_start, end=y_end), } - with pytest.raises(AssertionError): + with pytest.raises(ValueError, match="empty range"): ds = mdp.ops.selection.select_by_kwargs(ds, **coord_ranges) + + +def test_error_on_equal_range_bounds(): + ds = xr.Dataset(coords={"x": [0, 1, 2]}) + coord_ranges = {"x": mdp.config.Range(start=1, end=1)} + + with pytest.raises(ValueError, match="Start and end cannot be the same"): + mdp.ops.selection.select_by_kwargs(ds, **coord_ranges) From 02baa0fc0a685a106c52b3ea44306c6a9843148b Mon Sep 17 00:00:00 2001 From: pragnyanramtha Date: Wed, 13 May 2026 23:34:21 +0000 Subject: [PATCH 2/2] Clarify latlon helper input type --- mllam_data_prep/ops/cropping.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mllam_data_prep/ops/cropping.py b/mllam_data_prep/ops/cropping.py index d1a8c4e..de9207c 100644 --- a/mllam_data_prep/ops/cropping.py +++ b/mllam_data_prep/ops/cropping.py @@ -6,13 +6,13 @@ from spherical_geometry.polygon import SphericalPolygon -def _get_latlon_coords(da: xr.DataArray) -> tuple: +def _get_latlon_coords(da: Union[xr.Dataset, xr.DataArray]) -> tuple: """ - Get the latlon coordinates of a DataArray. + Get the latlon coordinates of a Dataset or DataArray. Parameters ---------- - da : xarray.DataArray + da : xarray.Dataset or xarray.DataArray The data. Returns @@ -25,7 +25,7 @@ def _get_latlon_coords(da: xr.DataArray) -> tuple: elif "lat" in da.coords and "lon" in da.coords: return (da.lon, da.lat) else: - raise Exception("Could not find lat/lon coordinates in DataArray.") + raise Exception("Could not find lat/lon coordinates.") def create_convex_hull_mask(ds: xr.Dataset, ds_reference: xr.Dataset) -> xr.DataArray: