From a2677f44324d1dd656478d109afd9e78a9f5f41c Mon Sep 17 00:00:00 2001 From: tomvothecoder Date: Wed, 20 Mar 2024 16:52:49 -0500 Subject: [PATCH 1/8] Remove deprecated features and APIs - Remove `horizontal_xesmf()` and `horizontal_regrid2()` - Remove `**kwargs` from `create_grid()` and `_deprecated_create_grid()` - Remove `add_bounds` accepting boolean arg in `open_dataset()`, `open_mfdataset()` and `_postprocess_dataset()` --- xcdat/dataset.py | 102 +------------------------------ xcdat/regridder/accessor.py | 118 ------------------------------------ xcdat/regridder/grid.py | 56 ----------------- 3 files changed, 2 insertions(+), 274 deletions(-) diff --git a/xcdat/dataset.py b/xcdat/dataset.py index f3fe3e01..a8e2092a 100644 --- a/xcdat/dataset.py +++ b/xcdat/dataset.py @@ -46,7 +46,7 @@ def open_dataset( path: str | os.PathLike[Any] | BufferedIOBase | AbstractDataStore, data_var: Optional[str] = None, - add_bounds: List[CFAxisKey] | None | bool = ["X", "Y"], + add_bounds: List[CFAxisKey] | None = ["X", "Y"], decode_times: bool = True, center_times: bool = False, lon_orient: Optional[Tuple[float, float]] = None, @@ -54,10 +54,6 @@ def open_dataset( ) -> xr.Dataset: """Wraps ``xarray.open_dataset()`` with post-processing options. - .. deprecated:: v0.6.0 - ``add_bounds`` boolean arguments (True/False) are being deprecated. - Please use either a list (e.g., ["X", "Y"]) to specify axes or ``None``. - Parameters ---------- path : str, Path, file-like or DataStore @@ -133,7 +129,7 @@ def open_dataset( def open_mfdataset( paths: str | NestedSequence[str | os.PathLike], data_var: Optional[str] = None, - add_bounds: List[CFAxisKey] | None | Literal[False] = ["X", "Y"], + add_bounds: List[CFAxisKey] | None = ["X", "Y"], decode_times: bool = True, center_times: bool = False, lon_orient: Optional[Tuple[float, float]] = None, @@ -143,10 +139,6 @@ def open_mfdataset( ) -> xr.Dataset: """Wraps ``xarray.open_mfdataset()`` with post-processing options. - .. deprecated:: v0.6.0 - ``add_bounds`` boolean arguments (True/False) are being deprecated. - Please use either a list (e.g., ["X", "Y"]) to specify axes or ``None``. - Parameters ---------- paths : str | NestedSequence[str | os.PathLike] @@ -240,15 +232,6 @@ def open_mfdataset( in-memory copy you are manipulating in xarray is modified: the original file on disk is never touched. - The CDAT "Climate Data Markup Language" (CDML) is a deprecated dialect of - XML with a defined set of attributes. CDML is still used by current and - former users of CDAT. To enable CDML users to adopt xCDAT more easily in - their workflows, xCDAT can parse XML/CDML files for the ``directory`` - to generate a glob or list of file paths. Refer to [4]_ for more information - on CDML. NOTE: This feature is deprecated in v0.6.0 and will be removed in - the subsequent release. CDAT (including cdms2/CDML) is in maintenance only - mode and marked for end-of-life by the end of 2023. - References ---------- .. [2] https://docs.xarray.dev/en/stable/generated/xarray.combine_nested.html @@ -258,13 +241,6 @@ def open_mfdataset( if isinstance(paths, str) or isinstance(paths, pathlib.Path): if os.path.isdir(paths): paths = _parse_dir_for_nc_glob(paths) - elif _is_xml_filepath(paths): - warnings.warn( - "`open_mfdataset()` will no longer support CDML/XML paths after " - "v0.6.0 because CDAT is marked for end-of-life at the end of 2023.", - DeprecationWarning, - ) - paths = _parse_xml_for_nc_glob(paths) preprocess = partial(_preprocess, decode_times=decode_times, callable=preprocess) @@ -422,58 +398,6 @@ def decode_time(dataset: xr.Dataset) -> xr.Dataset: return ds -def _is_xml_filepath(paths: str | pathlib.Path) -> bool: - """Checks if the ``paths`` argument is a path to an XML file. - - Parameters - ---------- - paths : str | pathlib.Path - A string or pathlib.Path represnting a file path. - - Returns - ------- - bool - """ - if isinstance(paths, str): - return paths.split(".")[-1] == "xml" - elif isinstance(paths, pathlib.Path): - return paths.parts[-1].endswith("xml") - - -def _parse_xml_for_nc_glob(xml_path: str | pathlib.Path) -> str | List[str]: - """ - Parses an XML file for the ``directory`` attr to return a string glob or - list of string file paths. - - Parameters - ---------- - xml_path : str | pathlib.Path - The XML file path. - - Returns - ------- - str | List[str] - A string glob of `*.nc` paths. - - """ - # `resolve_entities=False` and `no_network=True` guards against XXE attacks. - # Source: https://rules.sonarsource.com/python/RSPEC-2755 - parser = etree.XMLParser(resolve_entities=False, no_network=True) - tree = etree.parse(xml_path, parser) - root = tree.getroot() - - dir_attr = root.attrib.get("directory") - if dir_attr is None: - raise KeyError( - f"The XML file ({xml_path}) does not have a 'directory' attribute " - "that points to a directory of `.nc` dataset files." - ) - - glob_path = dir_attr + "/*.nc" - - return glob_path - - def _parse_dir_for_nc_glob(dir_path: str | pathlib.Path) -> str: """Parses a directory for a glob of `*.nc` paths. @@ -611,28 +535,6 @@ def _postprocess_dataset( if center_times: ds = center_times_func(dataset) - # TODO: Boolean (`True`/`False`) will be deprecated after v0.6.0. - if add_bounds is True: - add_bounds = ["X", "Y"] - warnings.warn( - ( - "`add_bounds=True` will be deprecated after v0.6.0. Please use a list " - "of axis strings instead (e.g., `add_bounds=['X', 'Y']`)." - ), - DeprecationWarning, - stacklevel=2, - ) - elif add_bounds is False: - add_bounds = None - warnings.warn( - ( - "`add_bounds=False` will be deprecated after v0.6.0. Please use " - "`add_bounds=None` instead." - ), - DeprecationWarning, - stacklevel=2, - ) - if add_bounds is not None: ds = ds.bounds.add_missing_bounds(axes=add_bounds) diff --git a/xcdat/regridder/accessor.py b/xcdat/regridder/accessor.py index b8a93c35..96dae3d7 100644 --- a/xcdat/regridder/accessor.py +++ b/xcdat/regridder/accessor.py @@ -117,124 +117,6 @@ def _get_axis_data( return coord_var, bounds_var - # TODO Either provide generic `horizontal` and `vertical` methods or tool specific - def horizontal_xesmf( - self, - data_var: str, - output_grid: xr.Dataset, - **options: Any, - ) -> xr.Dataset: - """ - Deprecated, will be removed with 0.7.0 release. - - Extends the xESMF library for horizontal regridding between structured - rectilinear and curvilinear grids. - - This method extends ``xESMF`` by automatically constructing the - ``xe.XESMFRegridder`` object, preserving source bounds, and generating - missing bounds. It regrids ``data_var`` in the dataset to - ``output_grid``. - - Option documentation :py:func:`xcdat.regridder.xesmf.XESMFRegridder` - - Parameters - ---------- - data_var: str - Name of the variable in the `xr.Dataset` to regrid. - output_grid : xr.Dataset - Dataset containing output grid. - options : Dict[str, Any] - Dictionary with extra parameters for the regridder. - - Returns - ------- - xr.Dataset - With the ``data_var`` variable on the grid defined in ``output_grid``. - - Raises - ------ - ValueError - If tool is not supported. - - Examples - -------- - - Generate output grid: - - >>> output_grid = xcdat.create_gaussian_grid(32) - - Regrid data to output grid using xesmf: - - >>> ds.regridder.horizontal_xesmf("ts", output_grid) - """ - warnings.warn( - "`horizontal_xesmf` will be deprecated in 0.7.x, please migrate to using " - "`horizontal(..., tool='xesmf')` method.", - DeprecationWarning, - stacklevel=2, - ) - - regridder = HORIZONTAL_REGRID_TOOLS["xesmf"](self._ds, output_grid, **options) - - return regridder.horizontal(data_var, self._ds) - - # TODO Either provide generic `horizontal` and `vertical` methods or tool specific - def horizontal_regrid2( - self, - data_var: str, - output_grid: xr.Dataset, - **options: Any, - ) -> xr.Dataset: - """ - Deprecated, will be removed with 0.7.0 release. - - Pure python implementation of CDAT's regrid2 horizontal regridder. - - Regrids ``data_var`` in dataset to ``output_grid`` using regrid2's - algorithm. - - Options documentation :py:func:`xcdat.regridder.regrid2.Regrid2Regridder` - - Parameters - ---------- - data_var: str - Name of the variable in the `xr.Dataset` to regrid. - output_grid : xr.Dataset - Dataset containing output grid. - options : Dict[str, Any] - Dictionary with extra parameters for the regridder. - - Returns - ------- - xr.Dataset - With the ``data_var`` variable on the grid defined in ``output_grid``. - - Raises - ------ - ValueError - If tool is not supported. - - Examples - -------- - Generate output grid: - - >>> output_grid = xcdat.create_gaussian_grid(32) - - Regrid data to output grid using regrid2: - - >>> ds.regridder.horizontal_regrid2("ts", output_grid) - """ - warnings.warn( - "`horizontal_regrid2` will be deprecated in 0.7.x, please migrate to using " - "`horizontal(..., tool='regrid2')` method.", - DeprecationWarning, - stacklevel=2, - ) - - regridder = HORIZONTAL_REGRID_TOOLS["regrid2"](self._ds, output_grid, **options) - - return regridder.horizontal(data_var, self._ds) - def horizontal( self, data_var: str, diff --git a/xcdat/regridder/grid.py b/xcdat/regridder/grid.py index b2b32564..7bcfc15b 100644 --- a/xcdat/regridder/grid.py +++ b/xcdat/regridder/grid.py @@ -454,14 +454,9 @@ def create_grid( ] ] = None, attrs: Optional[Dict[str, str]] = None, - **kwargs: CoordOptionalBnds, ) -> xr.Dataset: """Creates a grid dataset using the specified axes. - .. deprecated:: v0.6.0 - ``**kwargs`` argument is being deprecated, please migrate to - ``x``, ``y``, or ``z`` arguments to create future grids. - Parameters ---------- x : Optional[Union[xr.DataArray, Tuple[xr.DataArray]]] @@ -515,17 +510,6 @@ def create_grid( >>> ) >>> grid = create_grid(z=z) """ - if np.all([item is None for item in (x, y, z)]) and len(kwargs) == 0: - raise ValueError("Must pass at least 1 axis to create a grid.") - elif np.all([item is None for item in (x, y, z)]) and len(kwargs) > 0: - warnings.warn( - "**kwargs will be deprecated, see docstring and use 'x', 'y', or 'z' arguments", - DeprecationWarning, - stacklevel=2, - ) - - return _deprecated_create_grid(**kwargs) - axes = {"x": x, "y": y, "z": z} ds = xr.Dataset(attrs={} if attrs is None else attrs.copy()) @@ -557,46 +541,6 @@ def create_grid( return ds -def _deprecated_create_grid(**kwargs: CoordOptionalBnds) -> xr.Dataset: - coords = {} - data_vars = {} - - for name, data in kwargs.items(): - if name in VAR_NAME_MAP["X"]: - coord, bnds = _prepare_coordinate(name, data, **COORD_DEFAULT_ATTRS["X"]) - elif name in VAR_NAME_MAP["Y"]: - coord, bnds = _prepare_coordinate(name, data, **COORD_DEFAULT_ATTRS["Y"]) - elif name in VAR_NAME_MAP["Z"]: - coord, bnds = _prepare_coordinate(name, data, **COORD_DEFAULT_ATTRS["Z"]) - else: - raise ValueError( - f"Coordinate {name} is not valid, reference " - "`xcdat.axis.VAR_NAME_MAP` for valid options." - ) - - coords[name] = coord - - if bnds is not None: - bnds = bnds.copy() - - if isinstance(bnds, np.ndarray): - bnds = xr.DataArray( - name=f"{name}_bnds", - data=bnds.copy(), - dims=[name, "bnds"], - ) - - data_vars[bnds.name] = bnds - - coord.attrs["bounds"] = bnds.name - - grid = xr.Dataset(data_vars, coords=coords) - - grid = grid.bounds.add_missing_bounds(axes=["X", "Y"]) - - return grid - - def _prepare_coordinate(name: str, data: CoordOptionalBnds, **attrs: Any): if isinstance(data, tuple): coord, bnds = data[0], data[1] From bf34e2542c4c6f475c8e249e8c1426644285dbcb Mon Sep 17 00:00:00 2001 From: tomvothecoder Date: Wed, 20 Mar 2024 16:56:17 -0500 Subject: [PATCH 2/8] Remove tests for deprecated code - Remove `lxml` dependency --- conda-env/ci.yml | 1 - conda-env/dev.yml | 1 - tests/test_dataset.py | 110 ------------------------------------------ tests/test_regrid.py | 67 ------------------------- xcdat/dataset.py | 13 ----- 5 files changed, 192 deletions(-) diff --git a/conda-env/ci.yml b/conda-env/ci.yml index 455062e0..3df06815 100644 --- a/conda-env/ci.yml +++ b/conda-env/ci.yml @@ -10,7 +10,6 @@ dependencies: - cf_xarray >=0.7.3 # Constrained because https://github.com/xarray-contrib/cf-xarray/issues/467 - cftime - dask - - lxml # TODO: Remove this in v0.7.0 once cdml/XML support is dropped - netcdf4 - numpy >=1.23.0 # This version of numpy includes support for Python 3.11. - pandas diff --git a/conda-env/dev.yml b/conda-env/dev.yml index 83088470..bdcd3c48 100644 --- a/conda-env/dev.yml +++ b/conda-env/dev.yml @@ -10,7 +10,6 @@ dependencies: - cf_xarray >=0.7.3 # Constrained because https://github.com/xarray-contrib/cf-xarray/issues/467 - cftime - dask - - lxml # TODO: Remove this in v0.7.0 once cdml/XML support is dropped - netcdf4 - numpy >=1.23.0 # This version of numpy includes support for Python 3.11. - pandas diff --git a/tests/test_dataset.py b/tests/test_dataset.py index 74b5edcc..c83f6a81 100644 --- a/tests/test_dataset.py +++ b/tests/test_dataset.py @@ -324,48 +324,6 @@ def test_keeps_specified_var_and_preserves_bounds(self): assert result.identical(expected) - def test_raises_deprecation_warning_when_passing_add_bounds_true(self): - ds_no_bounds = generate_dataset( - decode_times=True, cf_compliant=True, has_bounds=False - ) - ds_no_bounds.to_netcdf(self.file_path) - - with warnings.catch_warnings(record=True) as w: - result = open_dataset(self.file_path, add_bounds=True) - - assert len(w) == 1 - assert issubclass(w[0].category, DeprecationWarning) - assert str(w[0].message) == ( - "`add_bounds=True` will be deprecated after v0.6.0. Please use a list " - "of axis strings instead (e.g., `add_bounds=['X', 'Y']`)." - ) - - expected = generate_dataset( - decode_times=True, cf_compliant=True, has_bounds=True - ) - expected = expected.drop_vars("time_bnds") - del expected["time"].attrs["bounds"] - - assert result.identical(expected) - - def test_raises_deprecation_warning_when_passing_add_bounds_false(self): - ds_no_bounds = generate_dataset( - decode_times=True, cf_compliant=True, has_bounds=False - ) - ds_no_bounds.to_netcdf(self.file_path) - - with warnings.catch_warnings(record=True) as w: - result = open_dataset(self.file_path, add_bounds=False) - - assert len(w) == 1 - assert issubclass(w[0].category, DeprecationWarning) - assert str(w[0].message) == ( - "`add_bounds=False` will be deprecated after v0.6.0. Please use " - "`add_bounds=None` instead." - ) - - assert result.identical(ds_no_bounds) - class TestOpenMfDataset: @pytest.fixture(autouse=True) @@ -416,74 +374,6 @@ def test_skips_adding_bounds(self): result = open_mfdataset(self.file_path1, add_bounds=None) assert result.identical(ds) - def test_raises_error_if_xml_does_not_have_root_directory_attr(self): - ds1 = generate_dataset(decode_times=False, cf_compliant=False, has_bounds=True) - ds1.to_netcdf(self.file_path1) - ds2 = generate_dataset(decode_times=False, cf_compliant=False, has_bounds=True) - ds2 = ds2.rename_vars({"ts": "tas"}) - ds2.to_netcdf(self.file_path2) - - # Create the XML file - xml_path = f"{self.dir}/datasets.xml" - page = etree.Element("dataset") - doc = etree.ElementTree(page) - doc.write(xml_path, xml_declaration=True, encoding="utf-16") - - with pytest.raises(KeyError): - open_mfdataset(xml_path, decode_times=True) - - def test_opens_datasets_from_xml_using_str_path(self): - ds1 = generate_dataset(decode_times=False, cf_compliant=False, has_bounds=True) - ds1.to_netcdf(self.file_path1) - ds2 = generate_dataset(decode_times=False, cf_compliant=False, has_bounds=True) - ds2 = ds2.rename_vars({"ts": "tas"}) - ds2.to_netcdf(self.file_path2) - - # Create the XML file - xml_path = f"{self.dir}/datasets.xml" - page = etree.Element("dataset", directory=str(self.dir)) - doc = etree.ElementTree(page) - doc.write(xml_path, xml_declaration=True, encoding="utf-16") - - result = open_mfdataset(xml_path, decode_times=True) - expected = ds1.merge(ds2) - - result.identical(expected) - - def test_opens_datasets_from_xml_raises_deprecation_warning(self): - ds1 = generate_dataset(decode_times=False, cf_compliant=False, has_bounds=True) - ds1.to_netcdf(self.file_path1) - ds2 = generate_dataset(decode_times=False, cf_compliant=False, has_bounds=True) - ds2 = ds2.rename_vars({"ts": "tas"}) - ds2.to_netcdf(self.file_path2) - - # Create the XML file - xml_path = f"{self.dir}/datasets.xml" - page = etree.Element("dataset", directory=str(self.dir)) - doc = etree.ElementTree(page) - doc.write(xml_path, xml_declaration=True, encoding="utf-16") - - with pytest.warns(DeprecationWarning): - open_mfdataset(xml_path, decode_times=True) - - def test_opens_datasets_from_xml_using_pathlib_path(self): - ds1 = generate_dataset(decode_times=False, cf_compliant=False, has_bounds=True) - ds1.to_netcdf(self.file_path1) - ds2 = generate_dataset(decode_times=False, cf_compliant=False, has_bounds=True) - ds2 = ds2.rename_vars({"ts": "tas"}) - ds2.to_netcdf(self.file_path2) - - # Create the XML file - xml_path = self.dir / "datasets.xml" - page = etree.Element("dataset", directory=str(self.dir)) - doc = etree.ElementTree(page) - doc.write(xml_path, xml_declaration=True, encoding="utf-16") - - result = open_mfdataset(xml_path, decode_times=True) - expected = ds1.merge(ds2) - - result.identical(expected) - def test_raises_error_if_directory_has_no_netcdf_files(self): with pytest.raises(ValueError): open_mfdataset(str(self.dir), decode_times=True) diff --git a/tests/test_regrid.py b/tests/test_regrid.py index 13de350b..9b639c41 100644 --- a/tests/test_regrid.py +++ b/tests/test_regrid.py @@ -891,73 +891,6 @@ def test_create_grid_wrong_axis_value(self): ): grid.create_grid(x=(self.lon, self.lon_bnds, self.lat)) # type: ignore[arg-type] - def test_deprecated_unexpected_coordinate(self): - lev = np.linspace(1000, 1, 2) - - with pytest.raises( - ValueError, - match="Coordinate mass is not valid, reference `xcdat.axis.VAR_NAME_MAP` for valid options.", - ): - grid.create_grid(lev=lev, mass=np.linspace(10, 20, 2)) - - def test_deprecated_create_grid_lev(self): - lev = np.linspace(1000, 1, 2) - lev_bnds = np.array([[1499.5, 500.5], [500.5, -498.5]]) - - with warnings.catch_warnings(record=True) as w: - new_grid = grid.create_grid(lev=(lev, lev_bnds)) - - assert len(w) == 1 - assert issubclass(w[0].category, DeprecationWarning) - assert ( - str(w[0].message) - == "**kwargs will be deprecated, see docstring and use 'x', 'y', or 'z' arguments" - ) - - assert np.array_equal(new_grid.lev, lev) - assert np.array_equal(new_grid.lev_bnds, lev_bnds) - - def test_deprecated_create_grid(self): - lat = np.array([-45, 0, 45]) - lon = np.array([30, 60, 90, 120, 150]) - lat_bnds = np.array([[-67.5, -22.5], [-22.5, 22.5], [22.5, 67.5]]) - lon_bnds = np.array([[15, 45], [45, 75], [75, 105], [105, 135], [135, 165]]) - - new_grid = grid.create_grid(lat=lat, lon=lon) - - assert np.array_equal(new_grid.lat, lat) - assert np.array_equal(new_grid.lat_bnds, lat_bnds) - assert new_grid.lat.units == "degrees_north" - assert np.array_equal(new_grid.lon, lon) - assert np.array_equal(new_grid.lon_bnds, lon_bnds) - assert new_grid.lon.units == "degrees_east" - - da_lat = xr.DataArray( - name="lat", - data=lat, - dims=["lat"], - attrs={"units": "degrees_north", "axis": "Y"}, - ) - da_lon = xr.DataArray( - name="lon", - data=lon, - dims=["lon"], - attrs={"units": "degrees_east", "axis": "X"}, - ) - da_lat_bnds = xr.DataArray(name="lat_bnds", data=lat_bnds, dims=["lat", "bnds"]) - da_lon_bnds = xr.DataArray(name="lon_bnds", data=lon_bnds, dims=["lon", "bnds"]) - - new_grid = grid.create_grid( - lat=(da_lat, da_lat_bnds), lon=(da_lon, da_lon_bnds) - ) - - assert np.array_equal(new_grid.lat, lat) - assert np.array_equal(new_grid.lat_bnds, lat_bnds) - assert new_grid.lat.units == "degrees_north" - assert np.array_equal(new_grid.lon, lon) - assert np.array_equal(new_grid.lon_bnds, lon_bnds) - assert new_grid.lon.units == "degrees_east" - def test_uniform_grid(self): new_grid = grid.create_uniform_grid(-90, 90, 4.0, -180, 180, 5.0) diff --git a/xcdat/dataset.py b/xcdat/dataset.py index a8e2092a..8fcba526 100644 --- a/xcdat/dataset.py +++ b/xcdat/dataset.py @@ -154,15 +154,6 @@ def open_mfdataset( If concatenation along more than one dimension is desired, then ``paths`` must be a nested list-of-lists (see [2]_ ``xarray.combine_nested`` for details). - * File path to an XML file with a ``directory`` attribute (e.g., - ``"path/to/files"``). If ``directory`` is set to a blank string - (""), then the current directory is substituted ("."). This option - is intended to support the CDAT CDML dialect of XML files, but it - can work with any XML file that has the ``directory`` attribute. - Refer to [4]_ for more information on CDML. NOTE: This feature is - deprecated in v0.6.0 and will be removed in the subsequent release. - CDAT (including cdms2/CDML) is in maintenance only mode and marked - for end-of-life by the end of 2023. add_bounds: List[CFAxisKey] | None | bool List of CF axes to try to add bounds for (if missing), by default ["X", "Y"]. Set to None to not add any missing bounds. Please note that @@ -481,10 +472,6 @@ def _postprocess_dataset( ) -> xr.Dataset: """Post-processes a Dataset object. - .. deprecated:: v0.6.0 - ``add_bounds`` boolean arguments (True/False) are being deprecated. - Please use either a list (e.g., ["X", "Y"]) to specify axes or ``None``. - Parameters ---------- dataset : xr.Dataset From 5dbf6f3ab8e775703a2251b2205c704117b2b599 Mon Sep 17 00:00:00 2001 From: tomvothecoder Date: Wed, 20 Mar 2024 17:00:05 -0500 Subject: [PATCH 3/8] Remove unused imports --- tests/test_dataset.py | 1 - tests/test_regrid.py | 1 - xcdat/dataset.py | 2 -- xcdat/regridder/accessor.py | 1 - xcdat/regridder/grid.py | 1 - 5 files changed, 6 deletions(-) diff --git a/tests/test_dataset.py b/tests/test_dataset.py index c83f6a81..612cd3c6 100644 --- a/tests/test_dataset.py +++ b/tests/test_dataset.py @@ -5,7 +5,6 @@ import numpy as np import pytest import xarray as xr -from lxml import etree from tests.fixtures import generate_dataset from xcdat._logger import _setup_custom_logger diff --git a/tests/test_regrid.py b/tests/test_regrid.py index 9b639c41..347fa6a6 100644 --- a/tests/test_regrid.py +++ b/tests/test_regrid.py @@ -1,7 +1,6 @@ import datetime import re import sys -import warnings from unittest import mock import numpy as np diff --git a/xcdat/dataset.py b/xcdat/dataset.py index 8fcba526..45774b48 100644 --- a/xcdat/dataset.py +++ b/xcdat/dataset.py @@ -3,7 +3,6 @@ import os import pathlib -import warnings from datetime import datetime from functools import partial from io import BufferedIOBase @@ -13,7 +12,6 @@ import xarray as xr from dateutil import parser from dateutil import relativedelta as rd -from lxml import etree from xarray.backends.common import AbstractDataStore from xarray.coding.cftime_offsets import get_date_type from xarray.coding.times import convert_times, decode_cf_datetime diff --git a/xcdat/regridder/accessor.py b/xcdat/regridder/accessor.py index 96dae3d7..b4b34d95 100644 --- a/xcdat/regridder/accessor.py +++ b/xcdat/regridder/accessor.py @@ -1,6 +1,5 @@ from __future__ import annotations -import warnings from typing import Any, List, Literal, Tuple import xarray as xr diff --git a/xcdat/regridder/grid.py b/xcdat/regridder/grid.py index 7bcfc15b..28b1a2bf 100644 --- a/xcdat/regridder/grid.py +++ b/xcdat/regridder/grid.py @@ -1,4 +1,3 @@ -import warnings from typing import Any, Dict, List, Optional, Tuple, Union import numpy as np From 68c58a8d2b97425ae6e285dec39c5767aa6a1610 Mon Sep 17 00:00:00 2001 From: tomvothecoder Date: Wed, 20 Mar 2024 17:04:37 -0500 Subject: [PATCH 4/8] Remove deprecated `_prepare_coordinate()` - Add ValueError conditional back to `create_grid()` --- xcdat/regridder/grid.py | 23 +++-------------------- 1 file changed, 3 insertions(+), 20 deletions(-) diff --git a/xcdat/regridder/grid.py b/xcdat/regridder/grid.py index 28b1a2bf..87fea019 100644 --- a/xcdat/regridder/grid.py +++ b/xcdat/regridder/grid.py @@ -509,6 +509,9 @@ def create_grid( >>> ) >>> grid = create_grid(z=z) """ + if np.all([item is None for item in (x, y, z)]): + raise ValueError("Must pass at least 1 axis to create a grid.") + axes = {"x": x, "y": y, "z": z} ds = xr.Dataset(attrs={} if attrs is None else attrs.copy()) @@ -540,26 +543,6 @@ def create_grid( return ds -def _prepare_coordinate(name: str, data: CoordOptionalBnds, **attrs: Any): - if isinstance(data, tuple): - coord, bnds = data[0], data[1] - else: - coord, bnds = data, None - - # ensure we make a copy - coord = coord.copy() - - if isinstance(coord, np.ndarray): - coord = xr.DataArray( - name=name, - data=coord, - dims=[name], - attrs=attrs, - ) - - return coord, bnds - - def create_axis( name: str, data: Union[List[Union[int, float]], np.ndarray], From 232b0abec4e63561c36e46f65185f78315b2c26b Mon Sep 17 00:00:00 2001 From: tomvothecoder Date: Wed, 20 Mar 2024 17:13:44 -0500 Subject: [PATCH 5/8] Fix tests and update docstring for `create_grid()` --- tests/test_dataset.py | 6 ------ tests/test_regrid.py | 33 +++++++++++---------------------- xcdat/regridder/grid.py | 30 +++++++----------------------- 3 files changed, 18 insertions(+), 51 deletions(-) diff --git a/tests/test_dataset.py b/tests/test_dataset.py index 612cd3c6..b3841dff 100644 --- a/tests/test_dataset.py +++ b/tests/test_dataset.py @@ -76,9 +76,6 @@ def test_skips_adding_bounds(self): ds = generate_dataset(decode_times=True, cf_compliant=True, has_bounds=False) ds.to_netcdf(self.file_path) - result = open_dataset(self.file_path, add_bounds=False) - assert result.identical(ds) - result = open_dataset(self.file_path, add_bounds=None) assert result.identical(ds) @@ -367,9 +364,6 @@ def test_skips_adding_bounds(self): ds = generate_dataset(decode_times=True, cf_compliant=True, has_bounds=False) ds.to_netcdf(self.file_path1) - result = open_mfdataset(self.file_path1, add_bounds=False) - assert result.identical(ds) - result = open_mfdataset(self.file_path1, add_bounds=None) assert result.identical(ds) diff --git a/tests/test_regrid.py b/tests/test_regrid.py index 347fa6a6..4b2c6c08 100644 --- a/tests/test_regrid.py +++ b/tests/test_regrid.py @@ -37,7 +37,8 @@ class TestXGCMRegridder: def setup(self): self.ds = fixtures.generate_lev_dataset() - self.output_grid = grid.create_grid(lev=np.linspace(10000, 2000, 2)) + z = grid.create_axis("lev", np.linspace(10000, 2000, 2), generate_bounds=False) + self.output_grid = grid.create_grid(z=z) def test_multiple_z_axes(self): self.ds = self.ds.assign_coords({"ilev": self.ds.lev.copy().rename("ilev")}) @@ -919,8 +920,8 @@ def test_gaussian_grid(self): def test_global_mean_grid(self): source_grid = grid.create_grid( - lat=np.array([-80, -40, 0, 40, 80]), - lon=np.array([0, 45, 90, 180, 270, 360]), + x=np.array([0, 45, 90, 180, 270, 360]), + y=np.array([-80, -40, 0, 40, 80]), ) mean_grid = grid.create_global_mean_grid(source_grid) @@ -1001,7 +1002,8 @@ def test_raises_error_for_global_mean_grid_if_an_axis_has_multiple_dimensions(se def test_zonal_grid(self): source_grid = grid.create_grid( - lat=np.array([-80, -40, 0, 40, 80]), lon=np.array([-160, -80, 80, 160]) + x=np.array([-160, -80, 80, 160]), + y=np.array([-80, -40, 0, 40, 80]), ) zonal_grid = grid.create_zonal_grid(source_grid) @@ -1126,7 +1128,9 @@ def test_horizontal(self): assert output_data.ts.shape == (15, 4, 4) def test_vertical(self): - output_grid = grid.create_grid(lev=np.linspace(10000, 2000, 2)) + z = grid.create_axis("lev", np.linspace(10000, 2000, 2), generate_bounds=False) + + output_grid = grid.create_grid(z=z) output_data = self.vertical_ds.regridder.vertical( "so", output_grid, tool="xgcm", method="linear" @@ -1142,7 +1146,8 @@ def test_vertical(self): assert output_data.so.shape == (15, 4, 4, 4) def test_vertical_multiple_z_axes(self): - output_grid = grid.create_grid(lev=np.linspace(10000, 2000, 2)) + z = grid.create_axis("lev", np.linspace(10000, 2000, 2), generate_bounds=False) + output_grid = grid.create_grid(z=z) self.vertical_ds = self.vertical_ds.assign_coords( {"ilev": self.vertical_ds.lev.copy().rename("ilev")} @@ -1244,22 +1249,6 @@ def test_vertical_tool_check(self, _get_input_grid): ): self.ac.vertical("ts", mock_data, tool="dummy", target_data=None) # type: ignore - @pytest.mark.filterwarnings("ignore:.*invalid value.*divide.*:RuntimeWarning") - def test_convenience_methods(self): - ds = fixtures.generate_dataset( - decode_times=True, cf_compliant=False, has_bounds=True - ) - - out_grid = grid.create_gaussian_grid(32) - - output_xesmf = ds.regridder.horizontal_xesmf("ts", out_grid, method="bilinear") - - assert output_xesmf.ts.shape == (15, 32, 65) - - output_regrid2 = ds.regridder.horizontal_regrid2("ts", out_grid) - - assert output_regrid2.ts.shape == (15, 32, 65) - class TestBase: def test_preserve_bounds(self): diff --git a/xcdat/regridder/grid.py b/xcdat/regridder/grid.py index 87fea019..3752b2ef 100644 --- a/xcdat/regridder/grid.py +++ b/xcdat/regridder/grid.py @@ -1,11 +1,10 @@ -from typing import Any, Dict, List, Optional, Tuple, Union +from typing import Dict, List, Optional, Tuple, Union import numpy as np import xarray as xr from xcdat.axis import COORD_DEFAULT_ATTRS, VAR_NAME_MAP, CFAxisKey, get_dim_coords from xcdat.bounds import create_bounds -from xcdat.regridder.base import CoordOptionalBnds # First 50 zeros for the bessel function # Taken from https://github.com/CDAT/cdms/blob/dd41a8dd3b5bac10a4bfdf6e56f6465e11efc51d/regrid2/Src/_regridmodule.c#L3390-L3402 @@ -434,35 +433,20 @@ def create_zonal_grid(grid: xr.Dataset) -> xr.Dataset: def create_grid( - x: Optional[ - Union[ - xr.DataArray, - Tuple[xr.DataArray, Optional[xr.DataArray]], - ] - ] = None, - y: Optional[ - Union[ - xr.DataArray, - Tuple[xr.DataArray, Optional[xr.DataArray]], - ] - ] = None, - z: Optional[ - Union[ - xr.DataArray, - Tuple[xr.DataArray, Optional[xr.DataArray]], - ] - ] = None, + x: xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None = None, + y: xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None = None, + z: xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None = None, attrs: Optional[Dict[str, str]] = None, ) -> xr.Dataset: """Creates a grid dataset using the specified axes. Parameters ---------- - x : Optional[Union[xr.DataArray, Tuple[xr.DataArray]]] + x : xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None Data with optional bounds to use for the "X" axis, by default None. - y : Optional[Union[xr.DataArray, Tuple[xr.DataArray]]] + y : xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None Data with optional bounds to use for the "Y" axis, by default None. - z : Optional[Union[xr.DataArray, Tuple[xr.DataArray]]] + z : xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None Data with optional bounds to use for the "Z" axis, by default None. attrs : Optional[Dict[str, str]] Custom attributes to be added to the generated `xr.Dataset`. From bf98ad2d4e882e7600a9c1c92f644f14116ebdb3 Mon Sep 17 00:00:00 2001 From: tomvothecoder Date: Wed, 20 Mar 2024 17:20:14 -0500 Subject: [PATCH 6/8] Fix `test_global_mean_grid` and `test_zonal_grid` --- tests/test_regrid.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/test_regrid.py b/tests/test_regrid.py index 4b2c6c08..193a22a6 100644 --- a/tests/test_regrid.py +++ b/tests/test_regrid.py @@ -919,10 +919,14 @@ def test_gaussian_grid(self): assert uneven_grid.lon.shape == (67,) def test_global_mean_grid(self): - source_grid = grid.create_grid( - x=np.array([0, 45, 90, 180, 270, 360]), - y=np.array([-80, -40, 0, 40, 80]), + x = grid.create_axis( + "lon", np.array([0, 45, 90, 180, 270, 360]), generate_bounds=True ) + y = grid.create_axis( + "lat", np.array([-80, -40, 0, 40, 80]), generate_bounds=True + ) + + source_grid = grid.create_grid(x=x, y=y) mean_grid = grid.create_global_mean_grid(source_grid) @@ -1001,10 +1005,14 @@ def test_raises_error_for_global_mean_grid_if_an_axis_has_multiple_dimensions(se grid.create_global_mean_grid(source_grid_with_2_lons) def test_zonal_grid(self): - source_grid = grid.create_grid( - x=np.array([-160, -80, 80, 160]), - y=np.array([-80, -40, 0, 40, 80]), + x = grid.create_axis( + "lon", np.array([-160, -80, 80, 160]), generate_bounds=True ) + y = grid.create_axis( + "lat", np.array([-80, -40, 0, 40, 80]), generate_bounds=True + ) + + source_grid = grid.create_grid(x=x, y=y) zonal_grid = grid.create_zonal_grid(source_grid) From 424bd11ace5f55ba73917276b0ae67bc80e0f6e6 Mon Sep 17 00:00:00 2001 From: tomvothecoder Date: Thu, 21 Mar 2024 11:47:45 -0500 Subject: [PATCH 7/8] Fix type annotation for Python 3.9 --- xcdat/regridder/grid.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xcdat/regridder/grid.py b/xcdat/regridder/grid.py index 3752b2ef..152b2272 100644 --- a/xcdat/regridder/grid.py +++ b/xcdat/regridder/grid.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Dict, List, Optional, Tuple, Union import numpy as np From c5cdab3f6f3213e2365544b593eae312066a4729 Mon Sep 17 00:00:00 2001 From: tomvothecoder Date: Thu, 21 Mar 2024 11:50:48 -0500 Subject: [PATCH 8/8] Update type annotation for --- xcdat/regridder/grid.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/xcdat/regridder/grid.py b/xcdat/regridder/grid.py index 152b2272..28600a6f 100644 --- a/xcdat/regridder/grid.py +++ b/xcdat/regridder/grid.py @@ -435,21 +435,24 @@ def create_zonal_grid(grid: xr.Dataset) -> xr.Dataset: def create_grid( - x: xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None = None, - y: xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None = None, - z: xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None = None, + x: xr.DataArray | Tuple[xr.DataArray, xr.DataArray | None] | None = None, + y: xr.DataArray | Tuple[xr.DataArray, xr.DataArray | None] | None = None, + z: xr.DataArray | Tuple[xr.DataArray, xr.DataArray | None] | None = None, attrs: Optional[Dict[str, str]] = None, ) -> xr.Dataset: """Creates a grid dataset using the specified axes. Parameters ---------- - x : xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None - Data with optional bounds to use for the "X" axis, by default None. - y : xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None - Data with optional bounds to use for the "Y" axis, by default None. - z : xr.DataArray | Tuple[xr.DataArray, Optional[xr.DataArray]] | None - Data with optional bounds to use for the "Z" axis, by default None. + x : xr.DataArray | Tuple[xr.DataArray, xr.DataArray | None] | None + An optional dataarray or tuple of a datarray with optional bounds to use + for the "X" axis, by default None. + y : xr.DataArray | Tuple[xr.DataArray, xr.DataArray | None] | None = None, + An optional dataarray or tuple of a datarray with optional bounds to use + for the "Y" axis, by default None. + z : xr.DataArray | Tuple[xr.DataArray, xr.DataArray | None] | None + An optional dataarray or tuple of a datarray with optional bounds to use + for the "Z" axis, by default None. attrs : Optional[Dict[str, str]] Custom attributes to be added to the generated `xr.Dataset`.