Repository: pydap/pydap
Pull request #329 (2024-07-18 03:09:39)

include numpy attributes to BaseType to compute arraysize in bytes (uncompressed)
<!-- Thank you very much for your hard work and contribution! -->

The following Pull Request:

- [x] Adds the feature described in #328
- [x] Tests are added to demonstrate the new behavior, and all tests pass
on a local environment.


With this PR one can do this:

```python
from pydap.client import open_url
url = 'http://test.opendap.org:8080/opendap/podaac/LLC4320_pre-SWOT_Yongala_20121001.nc'

ds = open_url(url, protocol='dap4')
ds.nbytes
>>> 2448965660
```
This value is for uncompressed numpy array data. And so is much larger (almost 6x) than the value reported in the DAP Response Form: [445456145](http://test.opendap.org:8080/opendap/podaac/contents.html)




Patch:
diff --git a/src/pydap/model.py b/src/pydap/model.py
index dd90630..649d68e 100644
--- a/src/pydap/model.py
+++ b/src/pydap/model.py
@@ -175,7 +175,7 @@ from functools import reduce
 
 import numpy as np
 
-from .lib import _quote, decode_np_strings, tree
+from .lib import _quote, decode_np_strings, tree, walk
 
 __all__ = [
     "BaseType",
@@ -260,6 +260,8 @@ class BaseType(DapType):
         # these are set when not data is present (eg, when parsing a DDS)
         self._dtype = None
         self._shape = ()
+        self._itemsize = None
+        self._nbytes = None
 
     def __repr__(self):
         return "<%s with data %s>" % (type(self).__name__, repr(self.data))
@@ -291,6 +293,14 @@ class BaseType(DapType):
     def size(self):
         return int(np.prod(self.shape))
 
+    @property
+    def itemsize(self):
+        return np.asarray([], dtype=self.data.dtype).itemsize
+
+    @property
+    def nbytes(self):
+        return self.itemsize * self.size
+
     def __copy__(self):
         """A lightweight copy of the variable.
 
@@ -610,6 +620,16 @@ class DatasetType(StructureType):
     def change_order(self, order):
         self._dict = OrderedDict((k, self._dict[k]) for k in order)
 
+    @property
+    def nbytes(self):
+        """Returns the number of bytes occupied in aggregation by all
+        uncompressed, BaseType data in the DatasetType. Attributes are
+        not included in the computation of value"""
+        nbytes = 0
+        for var in walk(self, BaseType):
+            nbytes += var.nbytes
+        return nbytes
+
 
 class SequenceType(StructureType):
     """A container that stores data in a Numpy array.


Test patch:
diff --git a/src/pydap/tests/test_model.py b/src/pydap/tests/test_model.py
index b66f630..202c742 100644
--- a/src/pydap/tests/test_model.py
+++ b/src/pydap/tests/test_model.py
@@ -171,6 +171,17 @@ def test_BaseType_array():
     np.testing.assert_array_equal(np.array(var), np.arange(16).reshape(2, 2, 2, 2))
 
 
+def test_BaseType_itemsize():
+    """Test itemsize of array"""
+    var = BaseType("var", np.arange(4, dtype=np.int32))
+    assert var.itemsize == np.arange(1, dtype=np.int32).itemsize
+
+
+def test_BaseType_nbytes():
+    var = BaseType("var", np.arange(4, dtype=np.int32))
+    assert var.nbytes == np.arange(4, dtype=np.int32).nbytes
+
+
 # Test pydap structures.
 def test_StructureType_init():
     """Test attributes used for dict-like behavior."""
@@ -494,6 +505,22 @@ def gridtype_example():
     return example
 
 
+def test_DatasetType_nbytes(gridtype_example):
+    dataset = DatasetType("dataset")
+    dataset["example"] = gridtype_example
+    dataset["variable"] = BaseType("variable", np.arange(100, dtype=np.complex128))
+
+    a_nb = dataset["example"].a.nbytes
+    x_nb = dataset["example"].x.nbytes
+    y_nb = dataset["example"].y.nbytes
+    v_nb = dataset["variable"].nbytes
+
+    # aggregate size of arrays only
+    Nbytes = a_nb + x_nb + y_nb + v_nb
+
+    assert dataset.nbytes == Nbytes
+
+
 def test_GridType_repr(gridtype_example):
     """Test ``__repr__`` of grids."""
     assert repr(gridtype_example) == "<GridType with array 'a' and maps 'x', 'y'>"