Repository: CORE-GATECH-GROUP/serpent-tools (python)

Issue:
`getXY` for `DepletedMaterial` raises unhelpful value error if days missing from object
If a depleted material is asked to get some quantity for days that are not present, the following error is raised:
```
  File "C:\Users\ajohnson400\AppData\Local\Continuum\Anaconda3\lib\site-packages\serpenttools-0.1.1rc0-py3.5.egg\serpentTools\objects\__init__.py", line 162, in getXY
    else allY[rowId][:])
ValueError: could not broadcast input array from shape (19) into shape (0)
```

The value of `colIndices` is an empty list for this case.

Pull request:
Fixes #3; Cleaned up reader subclasses
If the reader requests days that are not present on the depleted material through the getXY method, a key error is raised. Added a unit test to verify this fix.

Fixes #3 

Signed-off-by: Andrew Johnson <1drew.e.johnson@gmail.com>

Patch:
diff --git a/serpentTools/__init__.py b/serpentTools/__init__.py
index 8af8d0f..d38090d 100644
--- a/serpentTools/__init__.py
+++ b/serpentTools/__init__.py
@@ -1,7 +1,7 @@
 from serpentTools import settings
 from serpentTools import parsers
 
-__version__ = '0.1.1'
+__version__ = '0.1.2'
 
 # List TODOS/feature requests here for now
 # Messages/Errors
diff --git a/serpentTools/objects/__init__.py b/serpentTools/objects/__init__.py
index 42ee6c0..8f2e2cd 100644
--- a/serpentTools/objects/__init__.py
+++ b/serpentTools/objects/__init__.py
@@ -146,8 +146,18 @@ class DepletedMaterial(_SupportingObject):
         AttributeError
             If the names of the isotopes have not been obtained and specific
             isotopes have been requested
+        KeyError
+            If at least one of the days requested is not present
         """
-        returnX = timePoints is None
+        if timePoints is not None:
+            returnX = False
+            timeCheck = self._checkTimePoints(xUnits, timePoints)
+            if any(timeCheck):
+                raise KeyError('The following times were not present in file {}'
+                               '\n{}'.format(self._container.filePath,
+                                             ', '.join(timeCheck)))
+        else:
+            returnX = True
         if names and 'names' not in self._container.metadata:
             raise AttributeError('Parser {} has not stored the isotope names.'
                                  .format(self._container))
@@ -164,6 +174,12 @@ class DepletedMaterial(_SupportingObject):
             return yVals, xVals
         return yVals
 
+    def _checkTimePoints(self, xUnits, timePoints):
+        valid = self[xUnits]
+        badPoints = [str(time) for time in timePoints if time not in valid]
+        return badPoints
+
+
     def _getXSlice(self, xUnits, timePoints):
         allX = self[xUnits]
         if timePoints is not None:
diff --git a/serpentTools/parsers/branching.py b/serpentTools/parsers/branching.py
index 6817810..8efd2f1 100644
--- a/serpentTools/parsers/branching.py
+++ b/serpentTools/parsers/branching.py
@@ -1,9 +1,9 @@
 """Parser responsible for reading the ``*coe.m`` files"""
 
-from serpentTools.parsers import _BaseReader
+from serpentTools.objects.readers import BaseReader
 
 
-class BranchingReader(_BaseReader):
+class BranchingReader(BaseReader):
     """
     Parser responsible for reading and working with automated branching files.
 
diff --git a/serpentTools/parsers/bumat.py b/serpentTools/parsers/bumat.py
index 70ead0a..27c155b 100644
--- a/serpentTools/parsers/bumat.py
+++ b/serpentTools/parsers/bumat.py
@@ -1,9 +1,9 @@
 """Parser responsible for reading the ``*bumat<n>.m`` files"""
 
-from serpentTools.parsers import _MaterialReader
+from serpentTools.objects.readers import MaterialReader
 
 
-class BumatReader(_MaterialReader):
+class BumatReader(MaterialReader):
     """
     Parser responsible for reading and working with burned material files.
 
diff --git a/serpentTools/parsers/detector.py b/serpentTools/parsers/detector.py
index ea40920..9c4c33c 100644
--- a/serpentTools/parsers/detector.py
+++ b/serpentTools/parsers/detector.py
@@ -1,9 +1,9 @@
 """Parser responsible for reading the ``*det<n>.m`` files"""
 
-from serpentTools.parsers import _BaseReader
+from serpentTools.objects.readers import BaseReader
 
 
-class DetectorReader(_BaseReader):
+class DetectorReader(BaseReader):
     """
     Parser responsible for reading and working with detector files.
 
diff --git a/serpentTools/parsers/fissionMatrix.py b/serpentTools/parsers/fissionMatrix.py
index 58af81d..3923415 100644
--- a/serpentTools/parsers/fissionMatrix.py
+++ b/serpentTools/parsers/fissionMatrix.py
@@ -1,9 +1,9 @@
 """Parser responsible for reading the ``*fmtx<n>.m`` files"""
 
-from serpentTools.parsers import _BaseReader
+from serpentTools.objects.readers import BaseReader
 
 
-class FissionMatrixReader(_BaseReader):
+class FissionMatrixReader(BaseReader):
     """
     Parser responsible for reading and working with fission matrix files.
 
diff --git a/serpentTools/parsers/results.py b/serpentTools/parsers/results.py
index f33b68c..ae4f1ef 100644
--- a/serpentTools/parsers/results.py
+++ b/serpentTools/parsers/results.py
@@ -1,9 +1,9 @@
 """Parser responsible for reading the ``*res.m`` files"""
 
-from serpentTools.parsers import _BaseReader
+from serpentTools.objects.readers import BaseReader
 
 
-class ResultsReader(_BaseReader):
+class ResultsReader(BaseReader):
     """
     Parser responsible for reading and working with result files.
 


Test patch:
diff --git a/serpentTools/tests/test_depletion.py b/serpentTools/tests/test_depletion.py
index 0574305..d7f6d52 100644
--- a/serpentTools/tests/test_depletion.py
+++ b/serpentTools/tests/test_depletion.py
@@ -156,6 +156,17 @@ class DepletedMaterialTester(_DepletionTestHelper):
                                                       names=['Xe135'])
         numpy.testing.assert_equal(actualDays, self.reader.metadata['days'])
 
+    def test_getXY_raisesError_badTime(self):
+        """Verify that a ValueError is raised for non-present requested days."""
+        badDays = [-1, 0, 50]
+        with self.assertRaises(KeyError):
+            self.material.getXY('days', 'adens', timePoints=badDays)
+
+    def test_fetchData(self):
+        """Verify that key errors are raised when bad data are requested."""
+        with self.assertRaises(KeyError):
+            _ = self.material['fake units']
+
 
 if __name__ == '__main__':
     unittest.main()