Repository: ecmwf/earthkit-data
Pull request #652 (2025-03-13 14:04:53)

Feature/merge main 0.13.3 into develop


Patch:
diff --git a/docs/release_notes/version_0.13_updates.rst b/docs/release_notes/version_0.13_updates.rst
index e7debcf..c7efb95 100644
--- a/docs/release_notes/version_0.13_updates.rst
+++ b/docs/release_notes/version_0.13_updates.rst
@@ -1,6 +1,14 @@
 Version 0.13 Updates
 /////////////////////////
 
+Version 0.13.3
+===============
+
+Fixes
+++++++++++
+
+- Fixed issue when could not convert ZIP containing a single NetCDF file to Xarray (:pr:`650`)
+
 
 Version 0.13.2
 ===============
diff --git a/src/earthkit/data/readers/directory.py b/src/earthkit/data/readers/directory.py
index 13084f1..1a280bc 100644
--- a/src/earthkit/data/readers/directory.py
+++ b/src/earthkit/data/readers/directory.py
@@ -15,7 +15,6 @@ import shutil
 from earthkit.data import from_source
 
 from . import Reader
-from . import reader as find_reader
 
 LOG = logging.getLogger(__name__)
 
@@ -59,8 +58,6 @@ class DirectoryReader(Reader):
                     self._content.append(full)
 
     def mutate(self):
-        if len(self._content) == 1:
-            return find_reader(self.source, self._content[0])
         return self
 
     def mutate_source(self):
@@ -69,6 +66,17 @@ class DirectoryReader(Reader):
                 raise ValueError("Cannot stream zarr directories")
             return from_source("zarr", self.path)
 
+        if len(self._content) == 1:
+            return from_source(
+                "file",
+                path=self._content[0],
+                filter=self.filter,
+                merger=self.merger,
+                stream=self.stream,
+                parts=self.parts,
+                **self.source._kwargs,
+            )
+
         return from_source(
             "multi",
             [


Test patch:
diff --git a/src/earthkit/data/testing.py b/src/earthkit/data/testing.py
index 9a0ea4f..1c0ac04 100644
--- a/src/earthkit/data/testing.py
+++ b/src/earthkit/data/testing.py
@@ -220,10 +220,18 @@ if not NO_CUPY:
 def make_tgz(target_dir, target_name, paths):
     import tarfile
 
-    tar = tarfile.open(os.path.join(target_dir, target_name), "w:gz")
-    for p in paths:
-        tar.add(p)
-    tar.close()
+    with tarfile.open(os.path.join(target_dir, target_name), "w:gz") as tar:
+        for p in paths:
+            tar.add(p)
+    # tar.close()
+
+
+def make_zip(target_dir, target_name, paths):
+    import zipfile
+
+    with zipfile.ZipFile(os.path.join(target_dir, target_name), "a") as zipf:
+        for p in paths:
+            zipf.write(p)
 
 
 WRITE_TO_FILE_METHODS = ["target", "save", "write"]
diff --git a/tests/sources/test_file.py b/tests/sources/test_file.py
index 0ec33d8..61f9b86 100644
--- a/tests/sources/test_file.py
+++ b/tests/sources/test_file.py
@@ -11,6 +11,7 @@
 
 import logging
 import os
+import shutil
 
 import pytest
 
@@ -19,6 +20,7 @@ from earthkit.data.core.temporary import temp_directory
 from earthkit.data.testing import WRITE_TO_FILE_METHODS
 from earthkit.data.testing import earthkit_examples_file
 from earthkit.data.testing import make_tgz
+from earthkit.data.testing import make_zip
 from earthkit.data.testing import preserve_cwd
 from earthkit.data.testing import write_to_file
 
@@ -325,6 +327,76 @@ def test_file_multi_directory_with_tar(write_method):
             assert ds.metadata(("param", "level")) == ref
 
 
+def test_file_grib_tar_with_single_file():
+    with temp_directory() as tmpdir:
+        path = os.path.join(tmpdir, "a.grib")
+        shutil.copy(earthkit_examples_file("test.grib"), path)
+        make_tgz(tmpdir, "test.tar.gz", [path])
+        t_path = os.path.join(tmpdir, "test.tar.gz")
+
+        ds = from_source("file", t_path)
+        assert len(ds) == 2, len(ds)
+        assert os.path.basename(ds.path) == "a.grib"
+
+
+def test_file_grib_zip_with_single_file():
+    with temp_directory() as tmpdir:
+        path = os.path.join(tmpdir, "a.grib")
+        shutil.copy(earthkit_examples_file("test.grib"), path)
+        make_zip(tmpdir, "test.zip", [path])
+        t_path = os.path.join(tmpdir, "test.zip")
+
+        ds = from_source("file", t_path)
+        assert len(ds) == 2, len(ds)
+        assert os.path.basename(ds.path) == "a.grib"
+
+
+def test_file_netcdf_tar_with_single_file():
+    with temp_directory() as tmpdir:
+        path = os.path.join(tmpdir, "a.nc")
+        shutil.copy(earthkit_examples_file("test.nc"), path)
+        make_tgz(tmpdir, "test.tar.gz", [path])
+        t_path = os.path.join(tmpdir, "test.tar.gz")
+
+        ds = from_source("file", t_path)
+        assert len(ds) == 2, len(ds)
+        assert os.path.basename(ds.path) == "a.nc"
+
+
+def test_file_netcdf_zip_with_single_file_1():
+    with temp_directory() as tmpdir:
+        path = os.path.join(tmpdir, "a.nc")
+        shutil.copy(earthkit_examples_file("test.nc"), path)
+        make_zip(tmpdir, "test.zip", [path])
+        ar_path = os.path.join(tmpdir, "test.zip")
+
+        ds = from_source("file", ar_path)
+        assert len(ds) == 2, len(ds)
+        assert os.path.basename(ds.path) == "a.nc"
+
+
+def test_file_netcdf_zip_with_single_file_2():
+    # This NetCDF is not read into a fieldlist
+    import xarray as xr
+
+    N = 3
+    ds_in = xr.DataArray(range(N), coords={"dim_0": range(N)}, name="foo").to_dataset()
+    # print(ds_in)
+
+    with temp_directory() as tmpdir:
+
+        path = os.path.join(tmpdir, "a.nc")
+        ds_in["foo"].to_netcdf(path)
+
+        make_zip(tmpdir, "test.zip", [path])
+        z_path = os.path.join(tmpdir, "test.zip")
+
+        ds = from_source("file", z_path)
+
+        # print(f"{ds=}")
+        assert os.path.basename(ds.path) == "a.nc"
+
+
 if __name__ == "__main__":
     from earthkit.data.testing import main