diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 05fd9636..eb2aaf23 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -2,6 +2,18 @@ Changelog for cfgrib ==================== +0.9.13.0 (2024-06-27) +--------------------- + +- Allow users to pass of list of values to filter a key by. + See `#384 `_. + +- Functionality to ignore keys when reading a grib file + See `#382 `_. + +- Preserve coordinate encoding in cfgrib.open_datasets + See `#381 `_. + 0.9.12.0 (2024-05-26) --------------------- diff --git a/cfgrib/__init__.py b/cfgrib/__init__.py index d442b6d0..459bfe4e 100644 --- a/cfgrib/__init__.py +++ b/cfgrib/__init__.py @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -__version__ = "0.9.12.0" +__version__ = "0.9.13.0" # cfgrib core API depends on the ECMWF ecCodes C-library only from .abc import Field, Fieldset, Index, MappingFieldset diff --git a/cfgrib/cfmessage.py b/cfgrib/cfmessage.py index c6d3500b..80a1805f 100644 --- a/cfgrib/cfmessage.py +++ b/cfgrib/cfmessage.py @@ -171,6 +171,10 @@ def build_valid_time(time, step): functools.partial(from_grib_date_time, date_key="indexingDate", time_key="indexingTime"), functools.partial(to_grib_date_time, date_key="indexingDate", time_key="indexingTime"), ), + "valid_month": ( + functools.partial(from_grib_date_time, date_key="monthlyVerificationDate", time_key="validityTime"), + functools.partial(to_grib_date_time, date_key="monthlyVerificationDate", time_key="validityTime"), + ), } # type: messages.ComputedKeysType diff --git a/cfgrib/dataset.py b/cfgrib/dataset.py index ddf1562a..c8ea895d 100644 --- a/cfgrib/dataset.py +++ b/cfgrib/dataset.py @@ -161,6 +161,7 @@ "verifying_time", "forecastMonth", "indexing_time", + "valid_month", ] SPECTRA_KEYS = ["directionNumber", "frequencyNumber"] @@ -251,6 +252,12 @@ "standard_name": "time", "long_name": "time", }, + "valid_month": { + "units": "seconds since 1970-01-01T00:00:00", + "calendar": "proleptic_gregorian", + "standard_name": "time", + "long_name": "time", + }, "verifying_time": { "units": "seconds since 1970-01-01T00:00:00", "calendar": "proleptic_gregorian", @@ -333,9 +340,9 @@ def get_values_in_order(message, shape): class OnDiskArray: index: abc.Index[T.Any, abc.Field] shape: T.Tuple[int, ...] - field_id_index: T.Dict[ - T.Tuple[T.Any, ...], T.List[T.Union[int, T.Tuple[int, int]]] - ] = attr.attrib(repr=False) + field_id_index: T.Dict[T.Tuple[T.Any, ...], T.List[T.Union[int, T.Tuple[int, int]]]] = ( + attr.attrib(repr=False) + ) missing_value: float geo_ndim: int = attr.attrib(default=1, repr=False) dtype = np.dtype("float32") @@ -458,10 +465,7 @@ def encode_cf_first(data_var_attrs, encode_cf=("parameter", "time"), time_dims=( if "GRIB_units" in data_var_attrs: data_var_attrs["units"] = data_var_attrs["GRIB_units"] if "time" in encode_cf: - if set(time_dims).issubset(ALL_REF_TIME_KEYS): - coords_map.extend(time_dims) - else: - raise ValueError("time_dims %r not a subset of %r" % (time_dims, ALL_REF_TIME_KEYS)) + coords_map.extend(time_dims) else: coords_map.extend(DATA_TIME_KEYS) coords_map.extend(VERTICAL_KEYS) @@ -491,6 +495,7 @@ def build_variable_components( read_keys: T.Iterable[str] = (), time_dims: T.Sequence[str] = ("time", "step"), extra_coords: T.Dict[str, str] = {}, + coords_as_attributes: T.Dict[str, str] = {}, cache_geo_coords: bool = True, ) -> T.Tuple[T.Dict[str, int], Variable, T.Dict[str, Variable]]: data_var_attrs = enforce_unique_attributes(index, DATA_ATTRIBUTES_KEYS, filter_by_keys) @@ -499,8 +504,9 @@ def build_variable_components( first = index.first() extra_attrs = read_data_var_attrs(first, extra_keys) data_var_attrs.update(**extra_attrs) - coords_map = encode_cf_first(data_var_attrs, encode_cf, time_dims) - + coords_map = encode_cf_first( + data_var_attrs, encode_cf, time_dims, + ) coord_name_key_map = {} coord_vars = {} for coord_key in coords_map: @@ -516,6 +522,9 @@ def build_variable_components( and "GRIB_typeOfLevel" in data_var_attrs ): coord_name = data_var_attrs["GRIB_typeOfLevel"] + if coord_name in coords_as_attributes and len(values) == 1: + data_var_attrs[f"GRIB_{coord_name}"] = values + continue coord_name_key_map[coord_name] = coord_key attributes = { "long_name": "original GRIB coordinate for key: %s(%s)" % (orig_name, coord_name), @@ -662,12 +671,21 @@ def build_dataset_components( read_keys: T.Iterable[str] = (), time_dims: T.Sequence[str] = ("time", "step"), extra_coords: T.Dict[str, str] = {}, + coords_as_attributes: T.Dict[str, str] = {}, cache_geo_coords: bool = True, ) -> T.Tuple[T.Dict[str, int], T.Dict[str, Variable], T.Dict[str, T.Any], T.Dict[str, T.Any]]: dimensions = {} # type: T.Dict[str, int] variables = {} # type: T.Dict[str, Variable] filter_by_keys = index.filter_by_keys + # Warn about time_dims here to prevent repeasted messages in build_variable_components + if errors != "ignore" and not set(time_dims).issubset(ALL_REF_TIME_KEYS): + log.warning( + "Not all time_dimensions are recognised, those which are not in the following list will not " + " be decoded as datetime objects:\n" + f"{ALL_REF_TIME_KEYS}" + ) + for param_id in index.get("paramId", []): var_index = index.subindex(paramId=param_id) try: @@ -680,6 +698,7 @@ def build_dataset_components( read_keys=read_keys, time_dims=time_dims, extra_coords=extra_coords, + coords_as_attributes=coords_as_attributes, cache_geo_coords=cache_geo_coords, ) except DatasetBuildError as ex: @@ -752,6 +771,7 @@ def open_fieldset( indexpath: T.Optional[str] = None, filter_by_keys: T.Dict[str, T.Any] = {}, read_keys: T.Sequence[str] = (), + ignore_keys: T.Sequence[str] = [], time_dims: T.Sequence[str] = ("time", "step"), extra_coords: T.Dict[str, str] = {}, computed_keys: messages.ComputedKeysType = cfmessage.COMPUTED_KEYS, @@ -763,6 +783,7 @@ def open_fieldset( log.warning(f"indexpath value {indexpath} is ignored") index_keys = compute_index_keys(time_dims, extra_coords, filter_by_keys) + index_keys = [key for key in index_keys if key not in ignore_keys] index = messages.FieldsetIndex.from_fieldset(fieldset, index_keys, computed_keys) filtered_index = index.subindex(filter_by_keys) return open_from_index(filtered_index, read_keys, time_dims, extra_coords, **kwargs) @@ -772,10 +793,12 @@ def open_fileindex( stream: messages.FileStream, indexpath: str = messages.DEFAULT_INDEXPATH, index_keys: T.Sequence[str] = INDEX_KEYS + ["time", "step"], + ignore_keys: T.Sequence[str] = [], filter_by_keys: T.Dict[str, T.Any] = {}, computed_keys: messages.ComputedKeysType = cfmessage.COMPUTED_KEYS, ) -> messages.FileIndex: index_keys = sorted(set(index_keys) | set(filter_by_keys)) + index_keys = [key for key in index_keys if key not in ignore_keys] index = messages.FileIndex.from_indexpath_or_filestream( stream, index_keys, indexpath=indexpath, computed_keys=computed_keys ) @@ -790,12 +813,12 @@ def open_file( read_keys: T.Sequence[str] = (), time_dims: T.Sequence[str] = ("time", "step"), extra_coords: T.Dict[str, str] = {}, + ignore_keys: T.Sequence[str] = [], **kwargs: T.Any, ) -> Dataset: """Open a GRIB file as a ``cfgrib.Dataset``.""" path = os.fspath(path) stream = messages.FileStream(path, errors=errors) index_keys = compute_index_keys(time_dims, extra_coords) - index = open_fileindex(stream, indexpath, index_keys, filter_by_keys=filter_by_keys) - + index = open_fileindex(stream, indexpath, index_keys, ignore_keys=ignore_keys, filter_by_keys=filter_by_keys) return open_from_index(index, read_keys, time_dims, extra_coords, errors=errors, **kwargs) diff --git a/cfgrib/messages.py b/cfgrib/messages.py index f7d725fb..d2d2000a 100644 --- a/cfgrib/messages.py +++ b/cfgrib/messages.py @@ -468,7 +468,10 @@ def subindex(self, filter_by_keys={}, **query): field_ids_index = [] for header_values, field_ids_values in self.field_ids_index: for idx, val in raw_query: - if header_values[idx] != val: + # Ensure that the values to be tested is a list or tuple + if not isinstance(val, (list, tuple)): + val = [val] + if header_values[idx] not in val: break else: field_ids_index.append((header_values, field_ids_values)) diff --git a/cfgrib/xarray_plugin.py b/cfgrib/xarray_plugin.py index a9268208..b5eea32c 100644 --- a/cfgrib/xarray_plugin.py +++ b/cfgrib/xarray_plugin.py @@ -99,11 +99,13 @@ def open_dataset( indexpath: str = messages.DEFAULT_INDEXPATH, filter_by_keys: T.Dict[str, T.Any] = {}, read_keys: T.Iterable[str] = (), + ignore_keys: T.Iterable[str] = (), encode_cf: T.Sequence[str] = ("parameter", "time", "geography", "vertical"), squeeze: bool = True, time_dims: T.Iterable[str] = ("time", "step"), errors: str = "warn", extra_coords: T.Dict[str, str] = {}, + coords_as_attributes: T.Dict[str, str] = {}, cache_geo_coords: bool = True, ) -> xr.Dataset: store = CfGribDataStore( @@ -111,12 +113,14 @@ def open_dataset( indexpath=indexpath, filter_by_keys=filter_by_keys, read_keys=read_keys, + ignore_keys=ignore_keys, encode_cf=encode_cf, squeeze=squeeze, time_dims=time_dims, lock=lock, errors=errors, extra_coords=extra_coords, + coords_as_attributes=coords_as_attributes, cache_geo_coords=cache_geo_coords, ) with xr.core.utils.close_on_error(store): diff --git a/ci/requirements-docs.txt b/ci/requirements-docs.txt index 5c7c5290..2b341afc 100644 --- a/ci/requirements-docs.txt +++ b/ci/requirements-docs.txt @@ -7,25 +7,25 @@ alabaster==0.7.12 # via sphinx attrs==19.3.0 babel==2.9.1 # via sphinx -certifi==2023.7.22 # via requests +certifi==2024.07.04 # via requests cffi==1.14.0 chardet==3.0.4 # via requests click==7.1.2 docutils==0.16 # via sphinx -idna==2.9 # via requests +idna==3.7 # via requests imagesize==1.2.0 # via sphinx -jinja2==2.11.3 # via sphinx +jinja2==3.1.4 # via sphinx markupsafe==1.1.1 # via jinja2 numpy==1.22.0 packaging==20.3 # via sphinx pandas==1.0.3 # via xarray pycparser==2.20 # via cffi -pygments==2.7.4 # via sphinx +pygments==2.15.0 # via sphinx pyparsing==2.4.7 # via packaging pytest-runner==5.2 python-dateutil==2.8.1 # via pandas pytz==2020.1 # via babel, pandas -requests==2.31.0 # via sphinx +requests==2.32.0 # via sphinx six==1.14.0 # via packaging, python-dateutil snowballstemmer==2.0.0 # via sphinx sphinx==3.0.3 @@ -35,5 +35,5 @@ sphinxcontrib-htmlhelp==1.0.3 # via sphinx sphinxcontrib-jsmath==1.0.1 # via sphinx sphinxcontrib-qthelp==1.0.3 # via sphinx sphinxcontrib-serializinghtml==1.1.4 # via sphinx -urllib3==1.26.5 # via requests +urllib3==1.26.19 # via requests xarray==0.15.1 diff --git a/ci/requirements-tests.txt b/ci/requirements-tests.txt index 78acff21..c8804874 100644 --- a/ci/requirements-tests.txt +++ b/ci/requirements-tests.txt @@ -19,7 +19,6 @@ packaging==20.3 # via pytest pandas==1.0.3 # via xarray pep8==1.7.1 # via pytest-pep8 pluggy==0.13.1 # via pytest -py==1.10.0 # via pytest pycparser==2.20 # via cffi pyflakes==2.2.0 # via pytest-flakes pyparsing==2.4.7 # via packaging @@ -29,7 +28,7 @@ pytest-flakes==4.0.0 pytest-mccabe==1.0 pytest-pep8==1.0.6 pytest-runner==5.2 -pytest==5.4.2 +pytest==7.2.0 python-dateutil==2.8.1 # via pandas pytz==2020.1 # via pandas scipy==1.8.0 @@ -37,4 +36,4 @@ six==1.14.0 # via packaging, python-dateutil toolz==0.10.0 # via dask wcwidth==0.1.9 # via pytest xarray==0.15.1 -zipp==3.1.0 # via importlib-metadata +zipp==3.19.1 # via importlib-metadata diff --git a/tests/environment-macos-3.8.yml b/tests/environment-macos-3.8.yml index 373d4774..876a23ad 100644 --- a/tests/environment-macos-3.8.yml +++ b/tests/environment-macos-3.8.yml @@ -90,7 +90,7 @@ dependencies: - python_abi=3.8 - pytz=2023.3 - readline=8.2 - - requests=2.31.0 + - requests=2.32.0 - scipy=1.10.1 - setuptools=68.0.0 - six=1.16.0 diff --git a/tests/environment-ubuntu-3.7.yml b/tests/environment-ubuntu-3.7.yml index e6c07009..832c17da 100644 --- a/tests/environment-ubuntu-3.7.yml +++ b/tests/environment-ubuntu-3.7.yml @@ -63,7 +63,7 @@ dependencies: - pip=21.2.2 - pluggy=1.0.0 - psutil=5.8.0 - - py=1.10.0 + - py=1.11.0 - pycparser=2.21 - pyparsing=3.0.4 - pytest=6.2.5 diff --git a/tests/environment-ubuntu-3.8.yml b/tests/environment-ubuntu-3.8.yml index f69a6be7..e5264e20 100644 --- a/tests/environment-ubuntu-3.8.yml +++ b/tests/environment-ubuntu-3.8.yml @@ -107,7 +107,7 @@ dependencies: - python_abi=3.8 - pytz=2023.3 - readline=8.2 - - requests=2.31.0 + - requests=2.32.0 - s2n=1.3.48 - scipy=1.10.1 - setuptools=68.0.0 diff --git a/tests/environment-windows-3.8.yml b/tests/environment-windows-3.8.yml index a9643991..7f46c593 100644 --- a/tests/environment-windows-3.8.yml +++ b/tests/environment-windows-3.8.yml @@ -48,7 +48,7 @@ dependencies: - pip=21.2.2 - pluggy=0.13.1 - psutil=5.8.0 - - py=1.10.0 + - py=1.11.0 - pycparser=2.21 - pyparsing=3.0.4 - pytest=6.2.4 diff --git a/tests/sample-data/cams-egg4-monthly.grib b/tests/sample-data/cams-egg4-monthly.grib new file mode 100644 index 00000000..5af77c33 Binary files /dev/null and b/tests/sample-data/cams-egg4-monthly.grib differ diff --git a/tests/sample-data/era5-levels-members.nc b/tests/sample-data/era5-levels-members.nc new file mode 100644 index 00000000..866410d3 Binary files /dev/null and b/tests/sample-data/era5-levels-members.nc differ diff --git a/tests/sample-data/soil-surface-level-mix.grib b/tests/sample-data/soil-surface-level-mix.grib new file mode 100644 index 00000000..a4dfe37b Binary files /dev/null and b/tests/sample-data/soil-surface-level-mix.grib differ diff --git a/tests/test_30_dataset.py b/tests/test_30_dataset.py index 5523d3ee..9914e482 100644 --- a/tests/test_30_dataset.py +++ b/tests/test_30_dataset.py @@ -13,6 +13,7 @@ TEST_DATA_SCALAR_TIME = os.path.join(SAMPLE_DATA_FOLDER, "era5-single-level-scalar-time.grib") TEST_DATA_ALTERNATE_ROWS = os.path.join(SAMPLE_DATA_FOLDER, "alternate-scanning.grib") TEST_DATA_MISSING_VALS = os.path.join(SAMPLE_DATA_FOLDER, "fields_with_missing_values.grib") +TEST_DATA_MULTI_PARAMS = os.path.join(SAMPLE_DATA_FOLDER, "multi_param_on_multi_dims.grib") def test_enforce_unique_attributes() -> None: @@ -131,6 +132,13 @@ def test_build_dataset_components_time_dims() -> None: assert dims == {"number": 28, "indexing_time": 2, "step": 20, "latitude": 6, "longitude": 11} +def test_build_dataset_components_ignore_keys() -> None: + stream = messages.FileStream(TEST_DATA_UKMO, "warn") + index = dataset.open_fileindex(stream, messages.DEFAULT_INDEXPATH, dataset.INDEX_KEYS) + assert "subCentre" in index.index_keys + index = dataset.open_fileindex(stream, messages.DEFAULT_INDEXPATH, dataset.INDEX_KEYS, ignore_keys=["subCentre"]) + assert "subCentre" not in index.index_keys + def test_Dataset() -> None: res = dataset.open_file(TEST_DATA) assert "Conventions" in res.attributes @@ -172,6 +180,14 @@ def test_Dataset_encode_cf_time() -> None: assert res.variables["t"].data[:, :, :, :].mean() > 0.0 +def test_Dataset_encode_ignore_keys() -> None: + res = dataset.open_file(TEST_DATA) + assert res.attributes["GRIB_edition"] == 1 + + res = dataset.open_file(TEST_DATA, ignore_keys=["edition"]) + assert "GRIB_edition" not in res.attributes + + def test_Dataset_encode_cf_geography() -> None: res = dataset.open_file(TEST_DATA, encode_cf=("geography",)) assert "history" in res.attributes @@ -303,12 +319,52 @@ def test_open_fieldset_computed_keys() -> None: assert np.array_equal(res.variables["2t"].data[()], np.array(fieldset[0]["values"])) +def test_open_fieldset_ignore_keys() -> None: + fieldset = { + -10: { + "gridType": "regular_ll", + "Nx": 2, + "Ny": 3, + "distinctLatitudes": [-10.0, 0.0, 10.0], + "distinctLongitudes": [0.0, 10.0], + "paramId": 167, + "shortName": "2t", + "subCentre": "test", + "values": [[1, 2], [3, 4], [5, 6]], + } + } + + res = dataset.open_fieldset(fieldset) + assert "GRIB_subCentre" in res.attributes + + res = dataset.open_fieldset(fieldset, ignore_keys="subCentre") + assert "GRIB_subCentre" not in res.attributes + def test_open_file() -> None: + res = dataset.open_file(TEST_DATA) + + assert "t" in res.variables + assert "z" in res.variables + + +def test_open_file_filter_by_keys() -> None: res = dataset.open_file(TEST_DATA, filter_by_keys={"shortName": "t"}) assert "t" in res.variables assert "z" not in res.variables + res = dataset.open_file(TEST_DATA_MULTI_PARAMS) + + assert "t" in res.variables + assert "z" in res.variables + assert "u" in res.variables + + res = dataset.open_file(TEST_DATA_MULTI_PARAMS, filter_by_keys={"shortName": ["t", "z"]}) + + assert "t" in res.variables + assert "z" in res.variables + assert "u" not in res.variables + def test_alternating_rows() -> None: res = dataset.open_file(TEST_DATA_ALTERNATE_ROWS) diff --git a/tests/test_50_sample_data.py b/tests/test_50_sample_data.py index 49765d28..9ea09a58 100644 --- a/tests/test_50_sample_data.py +++ b/tests/test_50_sample_data.py @@ -120,3 +120,12 @@ def test_dataset_missing_field_values() -> None: t2 = res.variables["t2m"] assert np.isclose(np.nanmean(t2[0, :, :]), 268.375) assert np.isclose(np.nanmean(t2[1, :, :]), 270.716) + + +def test_valid_month_time_dim() -> None: + + test_file = os.path.join(SAMPLE_DATA_FOLDER, "cams-egg4-monthly.grib") + ds = xr.open_dataset(test_file, time_dims=["valid_month"]) + + assert "valid_month" in ds.dims + diff --git a/tests/test_50_xarray_plugin.py b/tests/test_50_xarray_plugin.py index d638af62..2009198a 100644 --- a/tests/test_50_xarray_plugin.py +++ b/tests/test_50_xarray_plugin.py @@ -10,6 +10,8 @@ SAMPLE_DATA_FOLDER = os.path.join(os.path.dirname(__file__), "sample-data") TEST_DATA = os.path.join(SAMPLE_DATA_FOLDER, "regular_ll_sfc.grib") TEST_DATA_MISSING_VALS = os.path.join(SAMPLE_DATA_FOLDER, "fields_with_missing_values.grib") +TEST_DATA_MULTI_PARAMS = os.path.join(SAMPLE_DATA_FOLDER, "multi_param_on_multi_dims.grib") +TEST_DATA_MULTI_LEVTYPES = os.path.join(SAMPLE_DATA_FOLDER, "soil-surface-level-mix.grib") def test_plugin() -> None: @@ -29,6 +31,37 @@ def test_xr_open_dataset_file() -> None: assert list(ds.data_vars) == ["skt"] +def test_xr_open_dataset_file_filter_by_keys() -> None: + ds = xr.open_dataset(TEST_DATA_MULTI_PARAMS, engine="cfgrib") + + assert "t" in ds.data_vars + assert "z" in ds.data_vars + assert "u" in ds.data_vars + + ds = xr.open_dataset( + TEST_DATA_MULTI_PARAMS, engine="cfgrib", filter_by_keys={"shortName": "t"} + ) + + assert "t" in ds.data_vars + assert "z" not in ds.data_vars + assert "u" not in ds.data_vars + + ds = xr.open_dataset( + TEST_DATA_MULTI_PARAMS, engine="cfgrib", filter_by_keys={"shortName": ["t", "z"]} + ) + + assert "t" in ds.data_vars + assert "z" in ds.data_vars + assert "u" not in ds.data_vars + + +def test_xr_open_dataset_file_ignore_keys() -> None: + ds = xr.open_dataset(TEST_DATA, engine="cfgrib") + assert "GRIB_typeOfLevel" in ds["skt"].attrs + ds = xr.open_dataset(TEST_DATA, engine="cfgrib", ignore_keys=["typeOfLevel"]) + assert "GRIB_typeOfLevel" not in ds["skt"].attrs + + def test_xr_open_dataset_dict() -> None: fieldset = { -10: { @@ -49,6 +82,26 @@ def test_xr_open_dataset_dict() -> None: assert list(ds.data_vars) == ["2t"] +def test_xr_open_dataset_dict_ignore_keys() -> None: + fieldset = { + -10: { + "gridType": "regular_ll", + "Nx": 2, + "Ny": 3, + "distinctLatitudes": [-10.0, 0.0, 10.0], + "distinctLongitudes": [0.0, 10.0], + "paramId": 167, + "shortName": "2t", + "typeOfLevel": "surface", + "values": [[1, 2], [3, 4], [5, 6]], + } + } + ds = xr.open_dataset(fieldset, engine="cfgrib") + assert "GRIB_typeOfLevel" in ds["2t"].attrs + ds = xr.open_dataset(fieldset, engine="cfgrib", ignore_keys=["typeOfLevel"]) + assert "GRIB_typeOfLevel" not in ds["2t"].attrs + + def test_xr_open_dataset_list() -> None: fieldset = [ { @@ -73,6 +126,27 @@ def test_xr_open_dataset_list() -> None: assert ds_empty.equals(xr.Dataset()) +def test_xr_open_dataset_list_ignore_keys() -> None: + fieldset = [ + { + "gridType": "regular_ll", + "Nx": 2, + "Ny": 3, + "distinctLatitudes": [-10.0, 0.0, 10.0], + "distinctLongitudes": [0.0, 10.0], + "paramId": 167, + "shortName": "2t", + "typeOfLevel": "surface", + "values": [[1, 2], [3, 4], [5, 6]], + } + ] + + ds = xr.open_dataset(fieldset, engine="cfgrib") + assert "GRIB_typeOfLevel" in ds["2t"].attrs + ds = xr.open_dataset(fieldset, engine="cfgrib", ignore_keys=["typeOfLevel"]) + assert "GRIB_typeOfLevel" not in ds["2t"].attrs + + def test_read() -> None: expected = { "latitude": 37, @@ -91,3 +165,14 @@ def test_xr_open_dataset_file_missing_vals() -> None: t2 = ds["t2m"] assert np.isclose(np.nanmean(t2.values[0, :, :]), 268.375) assert np.isclose(np.nanmean(t2.values[1, :, :]), 270.716) + + +def test_xr_open_dataset_coords_to_attributes() -> None: + ds = xr.open_dataset( + TEST_DATA_MULTI_LEVTYPES, engine="cfgrib", coords_as_attributes=["surface", "depthBelowLandLayer"] + ) + assert "surface" not in ds.coords + assert "depthBelowLandLayer" not in ds.coords + + assert "GRIB_surface" in ds["t2m"].attrs + assert "GRIB_depthBelowLandLayer" in ds["stl1"].attrs \ No newline at end of file