Repository: pydap/pydap (python)

Issue:
make NetcdfHandler conform to the DMRParser data model
The `NetcdfHandler` is broadly used for testing, and so it has not received a lot of development/update. This is true compared to the dataset that results from the downloading DAP responses (via dmr parser). 
The differences lie in the attributes of the resulting dataset.

## Minimal Example

Consider [this remote dataset](http://test.opendap.org/opendap/dap4/SimpleGroup.nc4.h5.dmr.html)

```python
pyds.tree()
.SimpleGroup.nc4.h5
├──SimpleGroup
│  ├──Y
│  ├──X
│  ├──Temperature
│  └──Salinity
├──time
├──Z
├──Pressure
└──time_bnds
```
### DMR parser dataset
In particular the following are consistent with the desired DAP4 behavior:
```python
pyds.attributes
>>> {'description': 'A simple group for testing.'"
```
```python
pyds.dimensions
>>> {'time': 1, 'nv': 2, 'Z': 1000}
```
```python
ds['SimpleGroup/Temperature'].dims
>>> ['/time', 'Y', 'X']
```

### NetcdfHandler
Meanwhile 
```python
pyds = NetCDFHandler('SimpleGroup.nc4').dataset
pyds.attributes
>>> {'NC_GLOBAL': {'description': 'A simple group for testing.'},
 'dimensions': OrderedDict([('time', 1), ('nv', 2), ('Z', 1000)])}
```
```python
pyds.dimensions
>>> OrderedDict([('time', 1), ('nv', 2), ('Z', 1000)])
```
```python
pyds['SimpleGroup/Temperature'].dimensions
>>> ('/time', '/SimpleGroup/Y', '/SimpleGroup/X')
```
but `pyds['SimpleGroup/Temperature'].dims` yields a KeyError. 

### Summarizing
The following are the three changes that need to be implemented.

1. `pyds.attributes` Should be flat (no `NC_Global` container)
2. `pyds[key].dimensions` where `key` is a `Group` should return a dictionary.
3. `pyds[key].dims` where `key` is a BaseType (an array) should return a list and not a tuple. 


This should allow for better/easier testing in pydap and xarray.







Pull request:
update netcdf handler to be consistent with dmr parser when creating …
…datasets

<!-- Thank you very much for your hard work and contribution! -->

The following Pull Request:

- [x] Closes #478
- [x] Tests are updated to reflect the new change


Patch:
diff --git a/README.md b/README.md
index 8981750..b5d61a0 100644
--- a/README.md
+++ b/README.md
@@ -33,7 +33,7 @@ This will install pydap together with all the required dependencies.
 `pydap` is also available through [Anaconda](https://www.anaconda.com/). Below we install `pydap` and its required dependencies, along with common additional packages in a fresh conda environment named `"pydap"`:
 
 ```bash
-    $ conda create -n pydap -c conda-forge python=3.10 pydap numpy">=2.0" jupyterlab ipython netCDF4 scipy matplotlib
+    $ conda create -n pydap -c conda-forge python=3.11 pydap numpy jupyterlab ipython netCDF4 scipy matplotlib
 ```
 Now you simply activate the pydap environment:
 ```bash
diff --git a/src/pydap/handlers/netcdf/__init__.py b/src/pydap/handlers/netcdf/__init__.py
index a553b46..a3e4e86 100644
--- a/src/pydap/handlers/netcdf/__init__.py
+++ b/src/pydap/handlers/netcdf/__init__.py
@@ -58,7 +58,7 @@ class NetCDFHandler(BaseHandler):
                 dims = source.dimensions
 
                 # Add dimensions when creating the DatasetType
-                Dims = OrderedDict()
+                Dims = {}
                 fqn_dims = OrderedDict()  # keep track of fully qualifying names of dims
                 for dim in dims:
                     fqn_dims.update({"/" + dim: dim})
@@ -72,18 +72,18 @@ class NetCDFHandler(BaseHandler):
 
                 name = os.path.split(filepath)[1]
                 self.dataset = DatasetType(
-                    name, dimensions=Dims, attributes=dict(NC_GLOBAL=attrs(source))
+                    name, dimensions=Dims, attributes=dict(attrs(source))
                 )
 
                 # add grids
                 grids = [var for var in vars if var not in dims]
                 for grid in grids:
                     # make dimension a fully qualifying name
-                    dimensions = tuple(["/" + dim for dim in vars[grid].dimensions])
+                    dimensions = ["/" + dim for dim in vars[grid].dimensions]
                     self.dataset[grid] = BaseType(
                         grid,
                         LazyVariable(source[grid], grid, grid, self.filepath),
-                        dimensions=dimensions,
+                        dims=dimensions,
                         **attrs(vars[grid]),
                     )
 
@@ -99,6 +99,7 @@ class NetCDFHandler(BaseHandler):
                     data = vars[dim][:]
                     attributes = attrs(vars[dim])
                     self.dataset[dim] = BaseType(dim, data, None, attributes)
+                    self.dataset[dim].dims = ["/" + dim]
         except Exception as exc:
             raise
             message = "Unable to open file %s: %s" % (filepath, exc)
@@ -130,7 +131,7 @@ def group_fqn(_dataset, _source, _filepath, _fqn_dims=OrderedDict()):
             _path = _path + "/"
         # create group and attrs + dims (non-fqn)
         dims = _source[group].dimensions
-        Dims = OrderedDict()
+        Dims = {}
         for dim in dims:
             if dim not in _fqn_dims.items():
                 _fqn_dims.update({_path + dim: dim})
@@ -146,7 +147,8 @@ def group_fqn(_dataset, _source, _filepath, _fqn_dims=OrderedDict()):
         _attrs = dict(
             (attr, _source[group].getncattr(attr)) for attr in _source[group].ncattrs()
         )
-
+        if "path" in _attrs:
+            del _attrs["path"]
         _dataset.createGroup(_source[group].path, dimensions=Dims, **_attrs)
         # now vars
         Vars = _source[group].variables
@@ -163,10 +165,13 @@ def group_fqn(_dataset, _source, _filepath, _fqn_dims=OrderedDict()):
                 (attr, _source[group][var].getncattr(attr))
                 for attr in _source[group][var].ncattrs()
             )
+            if "path" in vattrs:
+                del vattrs["path"]
             _dataset.createVariable(
                 _path + var,
                 data=LazyVariable(data, var, _path + var, _filepath),
-                dimensions=tuple(vdims),
+                dims=vdims,
+                path=("/").join(_path.split("/")[:-1]),
                 **vattrs,
             )
         # check if there are nested group
diff --git a/src/pydap/model.py b/src/pydap/model.py
index f2ad384..f65e767 100644
--- a/src/pydap/model.py
+++ b/src/pydap/model.py
@@ -783,7 +783,8 @@ class DatasetType(StructureType):
                 )
             )
             return None
-
+        if "path" in attrs:
+            del attrs["path"]
         return self.createDapType(GroupType, name, path=path, **attrs)
 
     def createVariable(self, name, **attrs):
diff --git a/src/pydap/parsers/dmr.py b/src/pydap/parsers/dmr.py
index 24cdd23..5775301 100644
--- a/src/pydap/parsers/dmr.py
+++ b/src/pydap/parsers/dmr.py
@@ -303,8 +303,7 @@ def dmr_to_dataset(dmr):
         var_kwargs = {
             "name": pydap.lib._quote(name),
             "data": data,
-            "dimensions": Dims,
-            "dims": nqfDims,
+            "dims": Dims,
             "attributes": variable["attributes"],
         }
         if "maps" in variable.keys():


Test patch:
diff --git a/src/pydap/tests/test_handlers_netcdf.py b/src/pydap/tests/test_handlers_netcdf.py
index 3a3f64d..fed759c 100644
--- a/src/pydap/tests/test_handlers_netcdf.py
+++ b/src/pydap/tests/test_handlers_netcdf.py
@@ -145,7 +145,7 @@ def test_handler_array(simple_Group_data, simple_handler2):
     np.testing.assert_array_equal(temp.data, simple_Group_data)
     assert r_dims == {"time": 1, "nv": 2}
     assert g_dims == {"X": 4, "Y": 4}
-    assert temp.dimensions == ("/time", "/Group/Y", "/Group/X")
+    assert temp.dims == ["/time", "/Group/Y", "/Group/X"]
 
 
 def test_handler_nested_Group_array(simple_Group_data, simple_handler3):
@@ -159,7 +159,7 @@ def test_handler_nested_Group_array(simple_Group_data, simple_handler3):
     assert r_dims == {"time": 1}
     assert g_dims == {}
     assert sg_dims == {"X": 4, "Y": 4}
-    assert temp.dimensions == ("/time", "/Group/SubGroup/Y", "/Group/SubGroup/X")
+    assert temp.dims == ["/time", "/Group/SubGroup/Y", "/Group/SubGroup/X"]
 
 
 def test_handler_repeatdims_Group_array(simple_handler4):
@@ -167,14 +167,14 @@ def test_handler_repeatdims_Group_array(simple_handler4):
     dataset = simple_handler4.dataset
     r_dims = dataset.dimensions
     g_dims = dataset["Group1"].dimensions
-    var_g1 = dataset["Group1/group_1_var"].dimensions
+    var_g1 = dataset["Group1/group_1_var"].dims
     sg_dims = dataset["Group1/subgroup1"].dimensions
-    var_sg1 = dataset["Group1/subgroup1/subgroup1_var"].dimensions
+    var_sg1 = dataset["Group1/subgroup1/subgroup1_var"].dims
     assert r_dims == {"lat": 1, "lon": 2}
     assert g_dims == {}
     assert sg_dims == {"lat": 2}
-    assert var_g1 == ("/lat", "/lon")
-    assert var_sg1 == ("/Group1/subgroup1/lat", "/lon")
+    assert var_g1 == ["/lat", "/lon"]
+    assert var_sg1 == ["/Group1/subgroup1/lat", "/lon"]
 
 
 @pytest.fixture(scope="module")
diff --git a/src/pydap/tests/test_parsers_dmr.py b/src/pydap/tests/test_parsers_dmr.py
index a98a74f..58bf57f 100644
--- a/src/pydap/tests/test_parsers_dmr.py
+++ b/src/pydap/tests/test_parsers_dmr.py
@@ -78,9 +78,7 @@ class TestDMRParser(unittest.TestCase):
         dataset = load_dmr_file(
             "data/dmrs/20220102090000-JPL-L4_GHRSST-SSTfnd-MUR-GLOB-v02.0-fv04.1.dmr"
         )
-        self.assertEqual(
-            dataset["sea_ice_fraction"].dimensions, ["/time", "/lat", "/lon"]
-        )
+        self.assertEqual(dataset["sea_ice_fraction"].dims, ["/time", "/lat", "/lon"])
         self.assertEqual(dataset.attributes["Conventions"], "CF-1.7")
 
     def test_mod05(self):
@@ -88,7 +86,7 @@ class TestDMRParser(unittest.TestCase):
             "data/dmrs/MOD05_L2.A2019336.2315.061.2019337071952.hdf.dmr"
         )
         self.assertEqual(
-            dataset["Water_Vapor_Infrared"].dimensions,
+            dataset["Water_Vapor_Infrared"].dims,
             [
                 "/Cell_Along_Swath_5km",
                 "/Cell_Across_Swath_5km",
@@ -122,17 +120,6 @@ class TestDMRParser(unittest.TestCase):
         # assert nv is NOT a variable/array
         self.assertNotIn("nv", variables)
 
-    def test_non_fqn_dims(self):
-        """Test that highlights the different behavior between variable attribtues
-        `dims` (semi-fully qualifgying names) and `dimensions` (fully-qualifying name)
-        """
-        dataset = load_dmr_file("data/dmrs/SimpleGroup.dmr")
-        self.assertEqual(dataset["/SimpleGroup/Temperature"].dims, ["/time", "Y", "X"])
-        self.assertEqual(
-            dataset["/SimpleGroup/Temperature"].dimensions,
-            ["/time", "/SimpleGroup/Y", "/SimpleGroup/X"],
-        )
-
     def tests_FlatGroups(self):
         dataset = load_dmr_file("data/dmrs/SimpleGroupFlat.dmr")
         # pick a single variable Maps