Repository: pydap/pydap (python)

Issue:
make dap2 and dap4 datasets consistent in the way they reference dimensions for BaseType
In pydap, the dimensions of the container-like object like Grids, Groups, Dataset, etc are defined as follows:

```python
pyds['Group'].dimensions
>>> {'dim1': size, 'dim2': size, ..., }
```
Where `dim1`, `dim2`, ... are not fully qualifying names because they refer to local dimensions defined within the scope of the Group.

## BaseType behavior
In the BaseType pydap object, dimensions have a different syntax, because they can reference dimensions outside of the local Group. As a result, in dap4:

### DAP4
Pydap follows the syntax of the DMR, which defines the dimensions of an atomic type, as `dims`. And so:

```python
pyds['Array'].dims
>>> ['fqn_dim1', 'fqn_dim2', ...]
```
where `'fqn_dim1'` refers to the fully qualifying name of the dimension.

### DAP2
```python
pyds['Array'].dimensions
>>> ['dim1', 'dim2', ...]
```

## Proposed Solution
To be consistent with all protocols, the DAP2 should follow the behavior of DAP4. This is, `dims` and `dimensions` have different syntax and therefore the BaseType dimension names should be defined as `dims`.






Pull request:
update BaseType`s reference to dimensions in DAP2
<!-- Thank you very much for your hard work and contribution! -->

The following Pull Request:

- [x] Closes #480 
- [x] Tests are updated

Patch:
diff --git a/src/pydap/model.py b/src/pydap/model.py
index f5764c5..f2ad384 100644
--- a/src/pydap/model.py
+++ b/src/pydap/model.py
@@ -50,9 +50,9 @@ arbitrary attributes::
     >>> foo.units
     'm/s'
 
-    >>> foo.dimensions
-    ()
-    >>> foo.dimensions = ('time',)
+    >>> foo.dims
+    []
+    >>> foo.dims = ['time',]
 
 Second, `BaseType` can hold data objects other than Numpy arrays. There are
 more complex data objects, like `pydap.handlers.dap.BaseProxy`, which acts as a
@@ -96,7 +96,7 @@ variable::
 
     >>> rain = GridType('rain')
     >>> rain['rain'] = BaseType(
-    ...     'rain', np.arange(6).reshape(2, 3), dimensions=('y', 'x'))
+    ...     'rain', np.arange(6).reshape(2, 3), dims=['y', 'x'])
     >>> rain['x'] = BaseType('x', np.arange(3), units='degrees_east')
     >>> rain['y'] = BaseType('y', np.arange(2), units='degrees_north')
     >>> rain.array  #doctest: +ELLIPSIS
@@ -256,11 +256,11 @@ class BaseType(DapType):
     """A thin wrapper over Numpy arrays."""
 
     def __init__(
-        self, name="nameless", data=None, dimensions=None, attributes=None, **kwargs
+        self, name="nameless", data=None, dims=None, attributes=None, **kwargs
     ):
         super(BaseType, self).__init__(name, attributes, **kwargs)
         self.data = data
-        self.dimensions = dimensions or ()
+        self.dims = dims or []
         # these are set when not data is present (eg, when parsing a DDS)
         self._dtype = None
         self._shape = ()
@@ -318,9 +318,7 @@ class BaseType(DapType):
         dimensions, same name, and a view of the data.
 
         """
-        out = type(self)(
-            self.name, self.data, self.dimensions[:], self.attributes.copy()
-        )
+        out = type(self)(self.name, self.data, self.dims[:], self.attributes.copy())
         out.id = self.id
         return out
 
diff --git a/src/pydap/parsers/dds.py b/src/pydap/parsers/dds.py
index 54c9807..c5203a4 100644
--- a/src/pydap/parsers/dds.py
+++ b/src/pydap/parsers/dds.py
@@ -75,7 +75,7 @@ class DDSParser(SimpleParser):
         self.consume(r";")
 
         data = DummyData(parser_dtype, shape)
-        var = BaseType(name, data, dimensions=dimensions)
+        var = BaseType(name, data, dims=dimensions)
 
         return var
 
diff --git a/src/pydap/responses/dds.py b/src/pydap/responses/dds.py
index 1c8ad73..6457773 100644
--- a/src/pydap/responses/dds.py
+++ b/src/pydap/responses/dds.py
@@ -92,8 +92,8 @@ def _gridtype(var, level=0, sequence=0):
 def _basetype(var, level=0, sequence=0):
     shape = var.shape[sequence:]
 
-    if var.dimensions:
-        shape = "".join(map("[{0[0]} = {0[1]}]".format, zip(var.dimensions, shape)))
+    if var.dims:
+        shape = "".join(map("[{0[0]} = {0[1]}]".format, zip(var.dims, shape)))
     elif len(shape) == 1:
         shape = "[{0} = {1}]".format(var.name, shape[0])
     else:
diff --git a/src/pydap/responses/dmr.py b/src/pydap/responses/dmr.py
index e866e86..6ec12ec 100644
--- a/src/pydap/responses/dmr.py
+++ b/src/pydap/responses/dmr.py
@@ -120,7 +120,7 @@ def _basetype(var, level=0):
         name=var.name,
     )
     # get dimensions
-    for dim in var.dimensions:
+    for dim in var.dims:
         yield '{indent}<Dim name="{name}"/>\n'.format(
             indent=(level + 1) * INDENT, name=dim
         )
diff --git a/src/pydap/wsgi/functions.py b/src/pydap/wsgi/functions.py
index d21b281..2883380 100644
--- a/src/pydap/wsgi/functions.py
+++ b/src/pydap/wsgi/functions.py
@@ -154,30 +154,31 @@ def mean(dataset, var, axis=0):
         )
 
     axis = int(axis)
-    dims = tuple(dim for i, dim in enumerate(var.dimensions) if i != axis)
 
     # process basetype
     if isinstance(var, BaseType):
+        dims = tuple(dim for i, dim in enumerate(var.dims) if i != axis)
         return BaseType(
             name=var.name,
             data=np.mean(var.data[:], axis=axis),
-            dimensions=dims,
+            dims=dims,
             attributes=var.attributes,
         )
 
     # process grid
+    dims = tuple(dim for i, dim in enumerate(var.dimensions) if i != axis)
     out = GridType(name=var.name, attributes=var.attributes)
     out[var.array.name] = BaseType(
         name=var.array.name,
         data=np.mean(var.array.data[:], axis=axis),
-        dimensions=dims,
+        dims=dims,
         attributes=var.array.attributes,
     )
     for dim in dims:
         out[dim] = BaseType(
             name=dim,
             data=var[dim].data[:],
-            dimensions=(dim,),
+            dims=(dim,),
             attributes=var[dim].attributes,
         )
     return out


Test patch:
diff --git a/src/pydap/tests/datasets.py b/src/pydap/tests/datasets.py
index 463be9c..77dded4 100644
--- a/src/pydap/tests/datasets.py
+++ b/src/pydap/tests/datasets.py
@@ -133,9 +133,7 @@ SimpleStructure.createVariable(name="/types.U", data=np.array("test unicode", st
 # test grid
 rain = DatasetType("test")
 rain["rain"] = GridType("rain")
-rain["rain"]["rain"] = BaseType(
-    "rain", np.arange(6).reshape(2, 3), dimensions=("y", "x")
-)
+rain["rain"]["rain"] = BaseType("rain", np.arange(6).reshape(2, 3), dims=("y", "x"))
 rain["rain"]["x"] = BaseType("x", np.arange(3), units="degrees_east")
 rain["rain"]["y"] = BaseType("y", np.arange(2), units="degrees_north")
 
@@ -208,7 +206,7 @@ SimpleSequence["cast"].data = np.array(
 SimpleGrid = DatasetType("SimpleGrid", description="A simple grid for testing.")
 SimpleGrid["SimpleGrid"] = GridType("SimpleGrid")
 SimpleGrid["SimpleGrid"]["SimpleGrid"] = BaseType(
-    "SimpleGrid", np.arange(6).reshape(2, 3), dimensions=("y", "x")
+    "SimpleGrid", np.arange(6).reshape(2, 3), dims=("y", "x")
 )
 SimpleGrid["x"] = SimpleGrid["SimpleGrid"]["x"] = BaseType(
     "x", np.arange(3), axis="X", units="degrees_east"
@@ -231,7 +229,7 @@ SimpleGroup.createVariable(
     name="/SimpleGroup/Temperature",
     data=np.arange(10, 26, 1, dtype="f4").reshape(1, 4, 4),
     units="degrees_celsius",
-    dimensions=("/time", "/SimpleGroup/Y", "/SimpleGroup/X"),
+    dims=("/time", "/SimpleGroup/Y", "/SimpleGroup/X"),
     _FillValue=np.inf,
     ValidRange=[-10, 100],
 )
@@ -239,27 +237,27 @@ SimpleGroup.createVariable(
     name="/SimpleGroup/Salinity",
     data=30 * np.ones(16, dtype="f4").reshape(1, 4, 4),
     units="psu",
-    dimensions=("/time", "/SimpleGroup/Y", "/SimpleGroup/X"),
+    dims=("/time", "/SimpleGroup/Y", "/SimpleGroup/X"),
     _FillValue=np.nan,
     ValidRange=[0.0, 50.0],
 )
 SimpleGroup.createVariable(
-    name="/SimpleGroup/Y", data=np.arange(4, dtype="i2"), dimensions=("/SimpleGroup/Y",)
+    name="/SimpleGroup/Y", data=np.arange(4, dtype="i2"), dims=("/SimpleGroup/Y",)
 )
 SimpleGroup.createVariable(
-    name="/SimpleGroup/X", data=np.arange(4, dtype="i2"), dimensions=("/SimpleGroup/X",)
+    name="/SimpleGroup/X", data=np.arange(4, dtype="i2"), dims=("/SimpleGroup/X",)
 )
 SimpleGroup.createVariable(
     name="/time",
     data=np.array(0.5, dtype="f4"),
-    dimensions=("/time",),
+    dims=("/time",),
     attributes={
         "standard_name": "time",
         "bounds": "time_bnds",
     },
 )
 SimpleGroup.createVariable(
-    name="/time_bnds", data=np.arange(2, dtype="f4"), dimensions=("/time", "/nv")
+    name="/time_bnds", data=np.arange(2, dtype="f4"), dims=("/time", "/nv")
 )
 
 
@@ -267,7 +265,7 @@ SimpleGroup.createVariable(
 FaultyGrid = DatasetType("FaultyGrid", description="A faulty grid for testing.")
 FaultyGrid["FaultyGrid"] = GridType("FaultyGrid")
 FaultyGrid["FaultyGrid"]["FaultyGrid"] = BaseType(
-    "FaultyGrid", np.arange(6).reshape(2, 3), dimensions=("y", "x")
+    "FaultyGrid", np.arange(6).reshape(2, 3), dims=("y", "x")
 )
 FaultyGrid["x"] = FaultyGrid["FaultyGrid"]["x"] = BaseType(
     "x", np.arange(3), axis="X", code=1
diff --git a/src/pydap/tests/test_model.py b/src/pydap/tests/test_model.py
index 99c534f..7f6677f 100644
--- a/src/pydap/tests/test_model.py
+++ b/src/pydap/tests/test_model.py
@@ -86,14 +86,22 @@ def test_BaseType_no_data():
     """Test empty data and dimensions attributes."""
     var = BaseType("var")
     assert var.data is None
-    assert var.dimensions == ()
+    assert var.dims == []
 
 
 def test_BaseType_data_and_dimensions():
     """Test data and dimensions assignment."""
-    var = BaseType("var", [42], ("x",))
+    var = BaseType(
+        "var",
+        [42],
+        [
+            "x",
+        ],
+    )
     assert var.data == [42]
-    assert var.dimensions == ("x",)
+    assert var.dims == [
+        "x",
+    ]
 
 
 def test_BaseType_repr():
@@ -138,7 +146,7 @@ def test_BaseType_copy():
     # test attributes
     assert original.id == clone.id
     assert original.name == clone.name
-    assert original.dimensions == clone.dimensions
+    assert original.dims == clone.dims
     assert original.attributes == clone.attributes