Skip to content

TST: TestIERS_Auto.test_simple fails with ValueError #9600

@pllim

Description

@pllim

I started seeing this on master and it is also affects unrelated PRs. Example log: https://travis-ci.org/astropy/astropy/jobs/610931212

__________________________ TestIERS_Auto.test_simple ___________________________
self = <IERS_Auto length=228>
 year month  day  ...        dX_2000A              dY_2000A       NutFlag
                  ......          0.004                  0.17       P
   16     7    18 ...                 0.013                 0.165       P
index = 228
vals = <Row index=47>
 year month  day    MJD   PolPMFlag_A  PM_x_A  e_PM_x_A  PM_y_A  e_PM_y_A UT1Flag_A UT1_UTC_A  e_UT1_UT...   nan     nan       nan        nan        nan -0.2154534       P 0.197449 0.459255         P      nan      nan       0
mask = [False, False, False, False, False, False, ...]
    def insert_row(self, index, vals=None, mask=None):
        """Add a new row before the given ``index`` position in the table.
    
        The ``vals`` argument can be:
    
        sequence (e.g. tuple or list)
            Column values in the same order as table columns.
        mapping (e.g. dict)
            Keys corresponding to column names.  Missing values will be
            filled with np.zeros for the column dtype.
        `None`
            All values filled with np.zeros for the column dtype.
    
        The ``mask`` attribute should give (if desired) the mask for the
        values. The type of the mask should match that of the values, i.e. if
        ``vals`` is an iterable, then ``mask`` should also be an iterable
        with the same length, and if ``vals`` is a mapping, then ``mask``
        should be a dictionary.
    
        Parameters
        ----------
        vals : tuple, list, dict or `None`
            Use the specified values in the new row
        mask : tuple, list, dict or `None`
            Use the specified mask values in the new row
        """
        colnames = self.colnames
    
        N = len(self)
        if index < -N or index > N:
            raise IndexError("Index {} is out of bounds for table with length {}"
                             .format(index, N))
        if index < 0:
            index += N
    
        def _is_mapping(obj):
            """Minimal checker for mapping (dict-like) interface for obj"""
            attrs = ('__getitem__', '__len__', '__iter__', 'keys', 'values', 'items')
            return all(hasattr(obj, attr) for attr in attrs)
    
        if _is_mapping(vals) or vals is None:
            # From the vals and/or mask mappings create the corresponding lists
            # that have entries for each table column.
            if mask is not None and not _is_mapping(mask):
                raise TypeError("Mismatch between type of vals and mask")
    
            # Now check that the mask is specified for the same keys as the
            # values, otherwise things get really confusing.
            if mask is not None and set(vals.keys()) != set(mask.keys()):
                raise ValueError('keys in mask should match keys in vals')
    
            if vals and any(name not in colnames for name in vals):
                raise ValueError('Keys in vals must all be valid column names')
    
            vals_list = []
            mask_list = []
    
            for name in colnames:
                if vals and name in vals:
                    vals_list.append(vals[name])
                    mask_list.append(False if mask is None else mask[name])
                else:
                    col = self[name]
                    if hasattr(col, 'dtype'):
                        # Make a placeholder zero element of the right type which is masked.
                        # This assumes the appropriate insert() method will broadcast a
                        # numpy scalar to the right shape.
                        vals_list.append(np.zeros(shape=(), dtype=col.dtype))
    
                        # For masked table any unsupplied values are masked by default.
                        mask_list.append(self.masked and vals is not None)
                    else:
                        raise ValueError(f"Value must be supplied for column '{name}'")
    
            vals = vals_list
            mask = mask_list
    
        if isiterable(vals):
            if mask is not None and (not isiterable(mask) or _is_mapping(mask)):
                raise TypeError("Mismatch between type of vals and mask")
    
            if len(self.columns) != len(vals):
                raise ValueError('Mismatch between number of vals and columns')
    
            if mask is not None:
                if len(self.columns) != len(mask):
                    raise ValueError('Mismatch between number of masks and columns')
            else:
                mask = [False] * len(self.columns)
    
        else:
            raise TypeError('Vals must be an iterable or mapping or None')
    
        columns = self.TableColumns()
        try:
            # Insert val at index for each column
            for name, col, val, mask_ in zip(colnames, self.columns.values(), vals, mask):
                # If new val is masked and the existing column does not support masking
                # then upgrade the column to a mask-enabled type: either the table-level
                # default ColumnClass or else MaskedColumn.
                if mask_ and isinstance(col, Column) and not isinstance(col, MaskedColumn):
                    col_cls = (self.ColumnClass
                               if issubclass(self.ColumnClass, self.MaskedColumn)
                               else self.MaskedColumn)
                    col = col_cls(col, copy=False)
    
>               newcol = col.insert(index, val, axis=0)
astropy/table/table.py:2764: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <MaskedColumn name='NutFlag_A' dtype='str1' description='IERS (I) or Prediction (P) flag forBull. A nutation values' l...  I
  I
  I
  I
  I
  I
  I
  I
  I
  I
  I
  I
  I
...
  P
  P
  P
  P
  P
  P
  P
  P
  P
  P
  P
  P
  P
  P
  P
  P
obj = 228, values = masked, mask = None, axis = 0
    def insert(self, obj, values, mask=None, axis=0):
        """
        Insert values along the given axis before the given indices and return
        a new `~astropy.table.MaskedColumn` object.
    
        Parameters
        ----------
        obj : int, slice or sequence of ints
            Object that defines the index or indices before which ``values`` is
            inserted.
        values : array_like
            Value(s) to insert.  If the type of ``values`` is different
            from that of quantity, ``values`` is converted to the matching type.
            ``values`` should be shaped so that it can be broadcast appropriately
        mask : boolean array_like
            Mask value(s) to insert.  If not supplied then False is used.
        axis : int, optional
            Axis along which to insert ``values``.  If ``axis`` is None then
            the column array is flattened before insertion.  Default is 0,
            which will insert a row.
    
        Returns
        -------
        out : `~astropy.table.MaskedColumn`
            A copy of column with ``values`` and ``mask`` inserted.  Note that the
            insertion does not occur in-place: a new masked column is returned.
        """
        self_ma = self.data  # self viewed as MaskedArray
    
        if self.dtype.kind == 'O':
            # Even if values is array-like (e.g. [1,2,3]), insert as a single
            # object.  Numpy.insert instead inserts each element in an array-like
            # input individually.
            new_data = np.insert(self_ma.data, obj, None, axis=axis)
            new_data[obj] = values
        else:
>           self_ma = _expand_string_array_for_values(self_ma, values)
astropy/table/column.py:1377: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
arr = masked_array(data=['I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I', 'I',
                   'I', 'I', 'I', 'I', 'I', ...e, False, False, False,
                   False, False, False, False],
       fill_value='N',
            dtype='<U1')
values = masked
    def _expand_string_array_for_values(arr, values):
        """
        For string-dtype return a version of ``arr`` that is wide enough for ``values``.
        If ``arr`` is not string-dtype or does not need expansion then return ``arr``.
    
        Parameters
        ----------
        arr : np.ndarray
            Input array
        values : scalar or array-like
            Values for width comparison for string arrays
    
        Returns
        -------
        arr_expanded : np.ndarray
    
        """
        if arr.dtype.kind in ('U', 'S'):
            # Find the length of the longest string in the new values.
>           values_str_len = np.char.str_len(values).max()
astropy/table/column.py:147: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
args = (masked,), kwargs = {}, relevant_args = (masked,)
>   ???
<__array_function__ internals>:6: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
a = masked
    @array_function_dispatch(_unary_op_dispatcher)
    def str_len(a):
        """
        Return len(a) element-wise.
    
        Parameters
        ----------
        a : array_like of str or unicode
    
        Returns
        -------
        out : ndarray
            Output array of integers
    
        See also
        --------
        __builtin__.len
        """
>       return _vec_string(a, integer, '__len__')
E       TypeError: string operation on non-string array
/home/travis/miniconda/envs/test/lib/python3.7/site-packages/numpy/core/defchararray.py:288: TypeError
During handling of the above exception, another exception occurred:
self = <astropy.utils.iers.tests.test_iers.TestIERS_Auto object at 0x7ff7b1e54490>
    @pytest.mark.remote_data
    def test_simple(self):
    
        with iers.conf.set_temp('iers_auto_url', self.iers_a_url_1):
    
            dat = iers.IERS_Auto.open()
            assert dat['MJD'][0] == 57359.0 * u.d
            assert dat['MJD'][-1] == 57539.0 * u.d
    
            # Pretend we are accessing at a time 7 days after start of predictive data
            predictive_mjd = dat.meta['predictive_mjd']
            dat._time_now = Time(predictive_mjd, format='mjd') + 7 * u.d
    
            # Look at times before and after the test file begins.  0.1292905 is
            # the IERS-B value from MJD=57359.  The value in
            # finals2000A-2016-02-30-test has been replaced at this point.
            assert np.allclose(dat.ut1_utc(Time(50000, format='mjd').jd).value, 0.1292905)
            assert np.allclose(dat.ut1_utc(Time(60000, format='mjd').jd).value, -0.2246227)
    
            # Now pretend we are accessing at time 60 days after start of predictive data.
            # There will be a warning when downloading the file doesn't give new data
            # and an exception when extrapolating into the future with insufficient data.
            dat._time_now = Time(predictive_mjd, format='mjd') + 60 * u.d
            assert np.allclose(dat.ut1_utc(Time(50000, format='mjd').jd).value, 0.1292905)
            with catch_warnings(iers.IERSStaleWarning) as warns:
                with pytest.raises(ValueError) as err:
                    dat.ut1_utc(Time(60000, format='mjd').jd)
            assert 'interpolating from IERS_Auto using predictive values' in str(err.value)
            assert len(warns) == 1
            assert 'IERS_Auto predictive values are older' in str(warns[0].message)
    
            # Warning only if we are getting return status
            with catch_warnings(iers.IERSStaleWarning) as warns:
                dat.ut1_utc(Time(60000, format='mjd').jd, return_status=True)
            assert len(warns) == 1
            assert 'IERS_Auto predictive values are older' in str(warns[0].message)
    
            # Now set auto_max_age = None which says that we don't care how old the
            # available IERS-A file is.  There should be no warnings or exceptions.
            with iers.conf.set_temp('auto_max_age', None):
                with catch_warnings(iers.IERSStaleWarning) as warns:
                    dat.ut1_utc(Time(60000, format='mjd').jd)
                assert not warns
    
        # Now point to a later file with same values but MJD increased by
        # 60 days and see that things work.  dat._time_now is still the same value
        # as before, i.e. right around the start of predictive values for the new file.
        # (In other words this is like downloading the latest file online right now).
        with iers.conf.set_temp('iers_auto_url', self.iers_a_url_2):
    
            # Look at times before and after the test file begins.  This forces a new download.
            assert np.allclose(dat.ut1_utc(Time(50000, format='mjd').jd).value, 0.1292905)
>           assert np.allclose(dat.ut1_utc(Time(60000, format='mjd').jd).value, -0.3)
astropy/utils/iers/tests/test_iers.py:305: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
astropy/utils/iers/iers.py:271: in ut1_utc
    self.ut1_utc_source if return_status else None)
astropy/utils/iers/iers.py:354: in _interpolate
    self._refresh_table_as_needed(mjd)
astropy/utils/iers/iers.py:794: in _refresh_table_as_needed
    self.add_row(row)
astropy/table/table.py:2656: in add_row
    self.insert_row(len(self), vals, mask)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
self = <IERS_Auto length=228>
 year month  day  ...        dX_2000A              dY_2000A       NutFlag
                  ......          0.004                  0.17       P
   16     7    18 ...                 0.013                 0.165       P
index = 228
vals = <Row index=47>
 year month  day    MJD   PolPMFlag_A  PM_x_A  e_PM_x_A  PM_y_A  e_PM_y_A UT1Flag_A UT1_UTC_A  e_UT1_UT...   nan     nan       nan        nan        nan -0.2154534       P 0.197449 0.459255         P      nan      nan       0
mask = [False, False, False, False, False, False, ...]
    def insert_row(self, index, vals=None, mask=None):
        """Add a new row before the given ``index`` position in the table.
    
        The ``vals`` argument can be:
    
        sequence (e.g. tuple or list)
            Column values in the same order as table columns.
        mapping (e.g. dict)
            Keys corresponding to column names.  Missing values will be
            filled with np.zeros for the column dtype.
        `None`
            All values filled with np.zeros for the column dtype.
    
        The ``mask`` attribute should give (if desired) the mask for the
        values. The type of the mask should match that of the values, i.e. if
        ``vals`` is an iterable, then ``mask`` should also be an iterable
        with the same length, and if ``vals`` is a mapping, then ``mask``
        should be a dictionary.
    
        Parameters
        ----------
        vals : tuple, list, dict or `None`
            Use the specified values in the new row
        mask : tuple, list, dict or `None`
            Use the specified mask values in the new row
        """
        colnames = self.colnames
    
        N = len(self)
        if index < -N or index > N:
            raise IndexError("Index {} is out of bounds for table with length {}"
                             .format(index, N))
        if index < 0:
            index += N
    
        def _is_mapping(obj):
            """Minimal checker for mapping (dict-like) interface for obj"""
            attrs = ('__getitem__', '__len__', '__iter__', 'keys', 'values', 'items')
            return all(hasattr(obj, attr) for attr in attrs)
    
        if _is_mapping(vals) or vals is None:
            # From the vals and/or mask mappings create the corresponding lists
            # that have entries for each table column.
            if mask is not None and not _is_mapping(mask):
                raise TypeError("Mismatch between type of vals and mask")
    
            # Now check that the mask is specified for the same keys as the
            # values, otherwise things get really confusing.
            if mask is not None and set(vals.keys()) != set(mask.keys()):
                raise ValueError('keys in mask should match keys in vals')
    
            if vals and any(name not in colnames for name in vals):
                raise ValueError('Keys in vals must all be valid column names')
    
            vals_list = []
            mask_list = []
    
            for name in colnames:
                if vals and name in vals:
                    vals_list.append(vals[name])
                    mask_list.append(False if mask is None else mask[name])
                else:
                    col = self[name]
                    if hasattr(col, 'dtype'):
                        # Make a placeholder zero element of the right type which is masked.
                        # This assumes the appropriate insert() method will broadcast a
                        # numpy scalar to the right shape.
                        vals_list.append(np.zeros(shape=(), dtype=col.dtype))
    
                        # For masked table any unsupplied values are masked by default.
                        mask_list.append(self.masked and vals is not None)
                    else:
                        raise ValueError(f"Value must be supplied for column '{name}'")
    
            vals = vals_list
            mask = mask_list
    
        if isiterable(vals):
            if mask is not None and (not isiterable(mask) or _is_mapping(mask)):
                raise TypeError("Mismatch between type of vals and mask")
    
            if len(self.columns) != len(vals):
                raise ValueError('Mismatch between number of vals and columns')
    
            if mask is not None:
                if len(self.columns) != len(mask):
                    raise ValueError('Mismatch between number of masks and columns')
            else:
                mask = [False] * len(self.columns)
    
        else:
            raise TypeError('Vals must be an iterable or mapping or None')
    
        columns = self.TableColumns()
        try:
            # Insert val at index for each column
            for name, col, val, mask_ in zip(colnames, self.columns.values(), vals, mask):
                # If new val is masked and the existing column does not support masking
                # then upgrade the column to a mask-enabled type: either the table-level
                # default ColumnClass or else MaskedColumn.
                if mask_ and isinstance(col, Column) and not isinstance(col, MaskedColumn):
                    col_cls = (self.ColumnClass
                               if issubclass(self.ColumnClass, self.MaskedColumn)
                               else self.MaskedColumn)
                    col = col_cls(col, copy=False)
    
                newcol = col.insert(index, val, axis=0)
    
                if len(newcol) != N + 1:
                    raise ValueError('Incorrect length for column {} after inserting {}'
                                     ' (expected {}, got {})'
                                     .format(name, val, len(newcol), N + 1))
                newcol.info.parent_table = self
    
                # Set mask if needed and possible
                if mask_:
                    if hasattr(newcol, 'mask'):
                        newcol[index] = np.ma.masked
                    else:
                        raise TypeError("mask was supplied for column '{}' but it does not "
                                        "support masked values".format(col.info.name))
    
                columns[name] = newcol
    
            # insert row in indices
            for table_index in self.indices:
                table_index.insert_row(index, vals, self.columns.values())
    
        except Exception as err:
            raise ValueError("Unable to insert row because of exception in column '{}':\n{}"
>                            .format(name, err))
E           ValueError: Unable to insert row because of exception in column 'NutFlag_A':
E           string operation on non-string array
astropy/table/table.py:2788: ValueError

cc @taldcroft @mhvk @aarchiba

Metadata

Metadata

Assignees

No one assigned

    Labels

    Affects-devPRs and issues that do not impact an existing Astropy releasetestingutils.iers

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions