Repository: ecmwf/earthkit-data (python)

Issue:
`from_source` is broken when readers are imported
### What happened?

I need to check in my code the object instantiated by `from_source`.
For example, I need to do this: `isinstance(ds, GRIBReader)`
However, when I import the reader `from_source` breaks.

### What are the steps to reproduce the bug?

```python
import earthkit.data
from earthkit.data.readers.grib.reader import GRIBReader

collection_id = "reanalysis-era5-single-levels"
request = {
    "variable": "2t",
    "product_type": "reanalysis",
    "date": "2012-12-01",
    "time": "12:00",
}
ds = earthkit.data.from_source("cds", collection_id, **request, format="grib") 
# TypeError: 'module' object is not callable 
```

### Version

0.7.0

### Platform (OS and architecture)

Linux eqc-quality-tools.eqc.compute.cci1.ecmwf.int 5.14.0-362.8.1.el9_3.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Nov 8 17:36:32 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux

### Relevant log output

```python
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[1], line 11
      4 collection_id = "reanalysis-era5-single-levels"
      5 request = {
      6     "variable": "2t",
      7     "product_type": "reanalysis",
      8     "date": "2012-12-01",
      9     "time": "12:00",
     10 }
---> 11 ds = earthkit.data.from_source("cds", collection_id, **request, format="grib")

File /data/common/miniforge3/envs/wp3/lib/python3.11/site-packages/earthkit/data/sources/__init__.py:155, in from_source(name, lazily, *args, **kwargs)
    153 while src is not prev:
    154     prev = src
--> 155     src = src.mutate()
    156 return src

File /data/common/miniforge3/envs/wp3/lib/python3.11/site-packages/earthkit/data/sources/file.py:77, in FileSource.mutate(self)
     73     return FileIndexedSource(self.path, filter=filter, merger=self.merger, **kw)
     75 # Give a chance to directories and zip files
     76 # to return a multi-source
---> 77 source = self._reader.mutate_source()
     78 if source not in (None, self):
     79     source._parent = self

File /data/common/miniforge3/envs/wp3/lib/python3.11/site-packages/earthkit/data/sources/file.py:96, in FileSource._reader(self)
     93 @property
     94 def _reader(self):
     95     if self._reader_ is None:
---> 96         self._reader_ = reader(
     97             self, self.path, content_type=self.content_type, parts=self.parts
     98         )
     99     return self._reader_

File /data/common/miniforge3/envs/wp3/lib/python3.11/site-packages/earthkit/data/readers/__init__.py:184, in reader(source, path, **kwargs)
    180     magic = f.read(n_bytes)
    182 LOG.debug("Looking for a reader for %s (%s)", path, magic)
--> 184 return _find_reader(
    185     "reader",
    186     source,
    187     path,
    188     magic=magic,
    189     **kwargs,
    190 )

File /data/common/miniforge3/envs/wp3/lib/python3.11/site-packages/earthkit/data/readers/__init__.py:130, in _find_reader(method_name, source, path_or_data, **kwargs)
    126 for deeper_check in (False, True):
    127     # We do two passes, the second one
    128     # allow the plugin to look deeper in the buffer
    129     for name, r in _readers(method_name).items():
--> 130         reader = r(source, path_or_data, deeper_check=deeper_check, **kwargs)
    131         if reader is not None:
    132             return reader.mutate()

TypeError: 'module' object is not callable
```

### Accompanying data

_No response_

### Organisation

B-Open / CADS-EQC

Pull request:
Fix from_source called after importing GRIBReader
Fixes #375 

Please note that ` GRIBReader` now has to be imported as:

```python
from earthkit.data.readers.grib.file import GRIBReader
```

However, using this internal object in any user code is discouraged.

Patch:
diff --git a/src/earthkit/data/readers/grib/__init__.py b/src/earthkit/data/readers/grib/__init__.py
index 1368518..07ab382 100644
--- a/src/earthkit/data/readers/grib/__init__.py
+++ b/src/earthkit/data/readers/grib/__init__.py
@@ -29,7 +29,7 @@ def _is_default(magic, content_type):
 
 def reader(source, path, *, magic=None, deeper_check=False, parts=None, **kwargs):
     if _match_magic(magic, deeper_check):
-        from .reader import GRIBReader
+        from .file import GRIBReader
 
         return GRIBReader(source, path, parts=parts)
 


Test patch:
diff --git a/src/earthkit/data/readers/grib/reader.py b/src/earthkit/data/readers/grib/file.py
similarity index 100%
rename from src/earthkit/data/readers/grib/reader.py
rename to src/earthkit/data/readers/grib/file.py
diff --git a/tests/readers/test_csv_reader.py b/tests/readers/test_csv_reader.py
index 1c9a490..b40b47d 100644
--- a/tests/readers/test_csv_reader.py
+++ b/tests/readers/test_csv_reader.py
@@ -34,7 +34,7 @@ def test_csv_1():
 
     ds = s.to_xarray()
     assert len(ds) == 3
-    assert list(ds.variables) == ["index", "a", "b", "c"]
+    assert set(ds.variables) == set(["index", "a", "b", "c"])
 
 
 def test_csv_2():
@@ -51,7 +51,7 @@ def test_csv_2():
 
     df = s.to_pandas()
     assert len(df) == 3
-    assert list(df.columns) == ["a", "b", "c"]
+    assert set(df.columns) == set(["a", "b", "c"])
 
 
 def test_csv_3():
@@ -68,7 +68,7 @@ def test_csv_3():
 
     df = s.to_pandas()
     assert len(df) == 3
-    assert list(df.columns) == ["a", "b", "c"]
+    assert set(df.columns) == set(["a", "b", "c"])
 
 
 def test_csv_4():
@@ -86,7 +86,7 @@ def test_csv_4():
 
     df = s.to_pandas()
     assert len(df) == 3
-    assert list(df.columns) == ["a", "b", "c"]
+    assert set(df.columns) == set(["a", "b", "c"])
 
 
 def test_csv_5():
@@ -141,7 +141,7 @@ def test_csv_text_file():
 
     df = s.to_pandas()
     assert len(df) == 3
-    assert list(df.columns) == ["a", "b", "c"]
+    assert set(df.columns) == set(["a", "b", "c"])
 
 
 def test_csv_with_comment():
@@ -160,11 +160,11 @@ def test_csv_with_comment():
 
     df = s.to_pandas()
     assert len(df) == 3
-    assert list(df.columns) == ["a", "b", "c"]
+    assert set(df.columns) == set(["a", "b", "c"])
 
     ds = s.to_xarray()
     assert len(ds) == 3
-    assert list(ds.variables) == ["index", "a", "b", "c"]
+    assert set(ds.variables) == set(["index", "a", "b", "c"])
 
 
 def test_csv_mimetypes():
diff --git a/tests/sources/test_file.py b/tests/sources/test_file.py
index 403b699..04cb806 100644
--- a/tests/sources/test_file.py
+++ b/tests/sources/test_file.py
@@ -22,8 +22,9 @@ LOG = logging.getLogger(__name__)
 
 
 def test_file_source_grib():
+    from earthkit.data.readers.grib.file import GRIBReader
+
     s = from_source("file", earthkit_examples_file("test.grib"))
-    from earthkit.data.readers.grib.reader import GRIBReader
 
     assert isinstance(s, GRIBReader), s
     assert len(s) == 2