Repository: obspy/obspy

Issue:
1.2.0.rc3 error writing inv response / 'FloatWithUncertaintiesAndUnit' object has no attribute 'number'
Seems to be small bug writing out an inventory to stationxml but only when there's a response attached. Fails on my particular data but the example here https://docs.obspy.org/tutorial/code_snippets/stationxml_file_from_scratch.html fails the same way. Works with validate=False.

```
~/.local/lib/python3.6/site-packages/obspy-1.2.0rc3.post0+7.g76111dfdae.obspy.master-py3.6-linux-x86_64.egg/obspy/io/stationxml/core.py in _write_channel(parent, channel, level)
   1226 
   1227     if channel.response is not None:
-> 1228         _write_response(channel_elem, channel.response)
   1229 
   1230 

~/.local/lib/python3.6/site-packages/obspy-1.2.0rc3.post0+7.g76111dfdae.obspy.master-py3.6-linux-x86_64.egg/obspy/io/stationxml/core.py in _write_response(parent, resp)
   1328     # write response stages
   1329     for stage in resp.response_stages:
-> 1330         _write_response_stage(parent, stage)
   1331     _write_extra(parent, resp)
   1332 

~/.local/lib/python3.6/site-packages/obspy-1.2.0rc3.post0+7.g76111dfdae.obspy.master-py3.6-linux-x86_64.egg/obspy/io/stationxml/core.py in _write_response_stage(parent, stage)
   1379                                   "numerator", "Numerator",
   1380                                   additional_mapping={'number': 'number'},
-> 1381                                   unit=False)
   1382             _write_floattype_list(sub_, stage,
   1383                                   "denominator", "Denominator",

~/.local/lib/python3.6/site-packages/obspy-1.2.0rc3.post0+7.g76111dfdae.obspy.master-py3.6-linux-x86_64.egg/obspy/io/stationxml/core.py in _write_floattype_list(parent, obj, attr_list_name, tag, additional_mapping, unit)
   1050         attribs["measurementMethod"] = obj_.measurement_method
   1051         for key1, key2 in additional_mapping.items():
-> 1052             attribs[key2] = getattr(obj_, key1)
   1053         attribs = {k: str(v) for k, v in attribs.items() if v is not None}
   1054         etree.SubElement(parent, tag, attribs).text = _float_to_str(obj_)

AttributeError: 'FloatWithUncertaintiesAndUnit' object has no attribute 'number'
```


1.2.0.rc3 error writing inv response / 'FloatWithUncertaintiesAndUnit' object has no attribute 'number'
Seems to be small bug writing out an inventory to stationxml but only when there's a response attached. Fails on my particular data but the example here https://docs.obspy.org/tutorial/code_snippets/stationxml_file_from_scratch.html fails the same way. Works with validate=False.

```
~/.local/lib/python3.6/site-packages/obspy-1.2.0rc3.post0+7.g76111dfdae.obspy.master-py3.6-linux-x86_64.egg/obspy/io/stationxml/core.py in _write_channel(parent, channel, level)
   1226 
   1227     if channel.response is not None:
-> 1228         _write_response(channel_elem, channel.response)
   1229 
   1230 

~/.local/lib/python3.6/site-packages/obspy-1.2.0rc3.post0+7.g76111dfdae.obspy.master-py3.6-linux-x86_64.egg/obspy/io/stationxml/core.py in _write_response(parent, resp)
   1328     # write response stages
   1329     for stage in resp.response_stages:
-> 1330         _write_response_stage(parent, stage)
   1331     _write_extra(parent, resp)
   1332 

~/.local/lib/python3.6/site-packages/obspy-1.2.0rc3.post0+7.g76111dfdae.obspy.master-py3.6-linux-x86_64.egg/obspy/io/stationxml/core.py in _write_response_stage(parent, stage)
   1379                                   "numerator", "Numerator",
   1380                                   additional_mapping={'number': 'number'},
-> 1381                                   unit=False)
   1382             _write_floattype_list(sub_, stage,
   1383                                   "denominator", "Denominator",

~/.local/lib/python3.6/site-packages/obspy-1.2.0rc3.post0+7.g76111dfdae.obspy.master-py3.6-linux-x86_64.egg/obspy/io/stationxml/core.py in _write_floattype_list(parent, obj, attr_list_name, tag, additional_mapping, unit)
   1050         attribs["measurementMethod"] = obj_.measurement_method
   1051         for key1, key2 in additional_mapping.items():
-> 1052             attribs[key2] = getattr(obj_, key1)
   1053         attribs = {k: str(v) for k, v in attribs.items() if v is not None}
   1054         etree.SubElement(parent, tag, attribs).text = _float_to_str(obj_)

AttributeError: 'FloatWithUncertaintiesAndUnit' object has no attribute 'number'
```




Discussion:
Thanks for reporting!
Thanks for reporting!

Resolving patch:
diff --git a/obspy/core/inventory/response.py b/obspy/core/inventory/response.py
--- a/obspy/core/inventory/response.py
+++ b/obspy/core/inventory/response.py
@@ -345,10 +345,10 @@ class CoefficientsTypeResponseStage(ResponseStage):
 
         The function tries to match inputs to one of three types if it can.
     :type numerator: list of
-        :class:`~obspy.core.util.obspy_types.FloatWithUncertaintiesAndUnit`
+        :class:`~obspy.core.util.obspy_types.CoefficientWithUncertainties`
     :param numerator: Numerator of the coefficient response stage.
     :type denominator: list of
-        :class:`~obspy.core.util.obspy_types.FloatWithUncertaintiesAndUnit`
+        :class:`~obspy.core.util.obspy_types.CoefficientWithUncertainties`
     :param denominator: Denominator of the coefficient response stage.
     """
     def __init__(self, stage_sequence_number, stage_gain,
@@ -406,12 +406,12 @@ def numerator(self, value):
             return
         value = list(value) if isinstance(
             value, compatibility.collections_abc.Iterable) else [value]
-        for _i, x in enumerate(value):
-            if not isinstance(x, FloatWithUncertaintiesAndUnit):
-                value[_i] = FloatWithUncertaintiesAndUnit(x)
-        if any(x.unit is not None for x in value):
+        if any(getattr(x, 'unit', None) is not None for x in value):
             msg = 'Setting Numerator/Denominator with a unit is deprecated.'
             warnings.warn(msg, ObsPyDeprecationWarning)
+        for _i, x in enumerate(value):
+            if not isinstance(x, CoefficientWithUncertainties):
+                value[_i] = CoefficientWithUncertainties(x)
         self._numerator = value
 
     @property
@@ -425,12 +425,12 @@ def denominator(self, value):
             return
         value = list(value) if isinstance(
             value, compatibility.collections_abc.Iterable) else [value]
-        for _i, x in enumerate(value):
-            if not isinstance(x, FloatWithUncertaintiesAndUnit):
-                value[_i] = FloatWithUncertaintiesAndUnit(x)
-        if any(x.unit is not None for x in value):
+        if any(getattr(x, 'unit', None) is not None for x in value):
             msg = 'Setting Numerator/Denominator with a unit is deprecated.'
             warnings.warn(msg, ObsPyDeprecationWarning)
+        for _i, x in enumerate(value):
+            if not isinstance(x, CoefficientWithUncertainties):
+                value[_i] = CoefficientWithUncertainties(x)
         self._denominator = value
 
     @property
diff --git a/obspy/io/stationxml/core.py b/obspy/io/stationxml/core.py
--- a/obspy/io/stationxml/core.py
+++ b/obspy/io/stationxml/core.py
@@ -616,11 +616,11 @@ def _tag2pole_or_zero(element):
             _tag2obj(elem, _ns("CfTransferFunctionType"), str)
         numerator = \
             _read_floattype_list(elem, _ns("Numerator"),
-                                 FloatWithUncertaintiesAndUnit, unit=True,
+                                 CoefficientWithUncertainties, unit=False,
                                  additional_mapping={"number": "number"})
         denominator = \
             _read_floattype_list(elem, _ns("Denominator"),
-                                 FloatWithUncertaintiesAndUnit, unit=True,
+                                 CoefficientWithUncertainties, unit=False,
                                  additional_mapping={"number": "number"})
         obj = obspy.core.inventory.CoefficientsTypeResponseStage(
             cf_transfer_function_type=cf_transfer_function_type,


Test patch:
diff --git a/obspy/io/stationxml/tests/data/full_random_stationxml.xml b/obspy/io/stationxml/tests/data/full_random_stationxml.xml
--- a/obspy/io/stationxml/tests/data/full_random_stationxml.xml
+++ b/obspy/io/stationxml/tests/data/full_random_stationxml.xml
@@ -592,10 +592,10 @@
                                 <Description>gTe8KIKiU3RfrWAJOg.CTy8f7Cz</Description>
                             </OutputUnits>
                             <CfTransferFunctionType>DIGITAL</CfTransferFunctionType>
-                            <Numerator plusError="-1096718463.14" minusError="1126505295.72">643096860.28</Numerator>
-                            <Numerator plusError="1487660393.16" minusError="1518618020.33">-1135677195.92</Numerator>
-                            <Denominator plusError="-1418173713.21" minusError="-696602068.55">1504792438.09</Denominator>
-                            <Denominator plusError="587099516.91" minusError="618144513.11">1711947659.78</Denominator>
+                            <Numerator number="1" plusError="-1096718463.14" minusError="1126505295.72">643096860.28</Numerator>
+                            <Numerator number="2" plusError="1487660393.16" minusError="1518618020.33">-1135677195.92</Numerator>
+                            <Denominator number="3" plusError="-1418173713.21" minusError="-696602068.55">1504792438.09</Denominator>
+                            <Denominator number="4" plusError="587099516.91" minusError="618144513.11">1711947659.78</Denominator>
                         </Coefficients>
                         <Decimation>
                             <InputSampleRate unit="HERTZ" plusError="59663358.91" minusError="-1077596674.88">-2002635152.29</InputSampleRate>
diff --git a/obspy/io/stationxml/tests/test_stationxml.py b/obspy/io/stationxml/tests/test_stationxml.py
--- a/obspy/io/stationxml/tests/test_stationxml.py
+++ b/obspy/io/stationxml/tests/test_stationxml.py
@@ -297,6 +297,13 @@ def test_read_and_write_full_file(self):
                 clear=['obspy.core.inventory.channel'],
                 expected=[(ObsPyDeprecationWarning, msg)]):
             self.assertEqual(cha.storage_format, None)
+        # check new number attributes on Numerator/Denominator
+        resp = cha.response
+        stage = resp.response_stages[1]
+        self.assertEqual(stage.numerator[0].number, 1)
+        self.assertEqual(stage.numerator[1].number, 2)
+        self.assertEqual(stage.denominator[0].number, 3)
+        self.assertEqual(stage.denominator[1].number, 4)
 
     def test_writing_module_tags(self):
         """