Skip to content

Commit

Permalink
Merge pull request #15396 from saimn/fits-deprecation
Browse files Browse the repository at this point in the history
Remove deprecated code and deprecate a few more things in io.fits
  • Loading branch information
pllim committed Oct 3, 2023
2 parents fff1973 + efab2cf commit de782c5
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 220 deletions.
18 changes: 12 additions & 6 deletions astropy/io/fits/hdu/base.py
Expand Up @@ -27,6 +27,7 @@
)
from astropy.io.fits.verify import _ErrList, _Verify
from astropy.utils import lazyproperty
from astropy.utils.decorators import deprecated
from astropy.utils.exceptions import AstropyUserWarning

__all__ = [
Expand Down Expand Up @@ -109,7 +110,11 @@ def _hdu_class_from_header(cls, header):
or c in cls._hdu_registry
):
continue
if c.match_header(header):
# skip _NonstandardExtHDU and _ExtensionHDU since those are deprecated
if c.match_header(header) and c not in (
_NonstandardExtHDU,
_ExtensionHDU,
):
klass = c
break
except NotImplementedError:
Expand Down Expand Up @@ -1613,9 +1618,9 @@ def _verify(self, option="warn"):
return errs


# For backwards compatibility, though this needs to be deprecated
# TODO: Mark this as deprecated
_ExtensionHDU = ExtensionHDU
@deprecated("v6.0")
class _ExtensionHDU(ExtensionHDU):
pass


class NonstandardExtHDU(ExtensionHDU):
Expand Down Expand Up @@ -1662,5 +1667,6 @@ def data(self):
return self._get_raw_data(self.size, "ubyte", self._data_offset)


# TODO: Mark this as deprecated
_NonstandardExtHDU = NonstandardExtHDU
@deprecated("v6.0")
class _NonstandardExtHDU(NonstandardExtHDU):
pass
62 changes: 7 additions & 55 deletions astropy/io/fits/hdu/table.py
Expand Up @@ -8,7 +8,6 @@
import re
import sys
import textwrap
import warnings
from contextlib import suppress

import numpy as np
Expand Down Expand Up @@ -38,7 +37,7 @@
from astropy.io.fits.header import Header, _pad_length
from astropy.io.fits.util import _is_int, _str_to_num, path_like
from astropy.utils import lazyproperty
from astropy.utils.exceptions import AstropyDeprecationWarning
from astropy.utils.decorators import deprecated

from .base import DELAYED, ExtensionHDU, _ValidHDU

Expand Down Expand Up @@ -351,40 +350,6 @@ def __init__(
else:
self.data = self._data_type.from_columns(data)

# TEMP: Special column keywords are normally overwritten by attributes
# from Column objects. In Astropy 3.0, several new keywords are now
# recognized as being special column keywords, but we don't
# automatically clear them yet, as we need to raise a deprecation
# warning for at least one major version.
if header is not None:
future_ignore = set()
for keyword in header.keys():
match = TDEF_RE.match(keyword)
try:
base_keyword = match.group("label")
except Exception:
continue # skip if there is no match
if base_keyword in {
"TCTYP",
"TCUNI",
"TCRPX",
"TCRVL",
"TCDLT",
"TRPOS",
}:
future_ignore.add(base_keyword)
if future_ignore:
keys = ", ".join(x + "n" for x in sorted(future_ignore))
warnings.warn(
"The following keywords are now recognized as special "
"column-related attributes and should be set via the "
f"Column objects: {keys}. In future, these values will be "
"dropped from manually specified headers automatically "
"and replaced with values generated based on the "
"Column objects.",
AstropyDeprecationWarning,
)

# TODO: Too much of the code in this class uses header keywords
# in making calculations related to the data size. This is
# unreliable, however, in cases when users mess with the header
Expand All @@ -395,7 +360,7 @@ def __init__(

self.columns = self.data._coldefs
self.columns._add_listener(self.data)
self.update()
self.update_header()

with suppress(TypeError, AttributeError):
# Make the ndarrays in the Column objects of the ColDefs
Expand Down Expand Up @@ -484,7 +449,7 @@ def data(self, data):

self.columns = self.data.columns
self.columns._add_listener(self.data)
self.update()
self.update_header()

with suppress(TypeError, AttributeError):
# Make the ndarrays in the Column objects of the ColDefs
Expand Down Expand Up @@ -517,10 +482,11 @@ def _theap(self):
size = self._header["NAXIS1"] * self._header["NAXIS2"]
return self._header.get("THEAP", size)

# TODO: Need to either rename this to update_header, for symmetry with the
# Image HDUs, or just at some point deprecate it and remove it altogether,
# since header updates should occur automatically when necessary...
@deprecated("v6.0", alternative="update_header")
def update(self):
self.update_header()

def update_header(self):
"""
Update header keywords to reflect recent changes of columns.
"""
Expand Down Expand Up @@ -702,20 +668,6 @@ def _clear_table_keywords(self, index=None):
continue # skip if there is no match

if base_keyword in KEYWORD_TO_ATTRIBUTE:
# TEMP: For Astropy 3.0 we don't clear away the following keywords
# as we are first raising a deprecation warning that these will be
# dropped automatically if they were specified in the header. We
# can remove this once we are happy to break backward-compatibility
if base_keyword in {
"TCTYP",
"TCUNI",
"TCRPX",
"TCRVL",
"TCDLT",
"TRPOS",
}:
continue

num = int(match.group("num")) - 1 # convert to zero-base
table_keywords.append((idx, match.group(0), base_keyword, num))

Expand Down
44 changes: 0 additions & 44 deletions astropy/io/fits/header.py
Expand Up @@ -1057,50 +1057,6 @@ def update(self, *args, **kwargs):
header.update({'NAXIS1': 100, 'NAXIS2': 100})
.. warning::
As this method works similarly to `dict.update` it is very
different from the ``Header.update()`` method in Astropy v0.1.
Use of the old API was
**deprecated** for a long time and is now removed. Most uses of the
old API can be replaced as follows:
* Replace ::
header.update(keyword, value)
with ::
header[keyword] = value
* Replace ::
header.update(keyword, value, comment=comment)
with ::
header[keyword] = (value, comment)
* Replace ::
header.update(keyword, value, before=before_keyword)
with ::
header.insert(before_keyword, (keyword, value))
* Replace ::
header.update(keyword, value, after=after_keyword)
with ::
header.insert(after_keyword, (keyword, value),
after=True)
See also :meth:`Header.set` which is a new method that provides an
interface similar to the old ``Header.update()`` and may help make
transition a little easier.
"""
if args:
other = args[0]
Expand Down
11 changes: 10 additions & 1 deletion astropy/io/fits/tests/test_core.py
Expand Up @@ -25,7 +25,7 @@
HAS_BZ2, # NOTE: Python can be built without bz2
)
from astropy.utils.data import conf
from astropy.utils.exceptions import AstropyUserWarning
from astropy.utils.exceptions import AstropyDeprecationWarning, AstropyUserWarning
from astropy.utils.misc import _NOT_OVERWRITING_MSG_MATCH

from .conftest import FitsTestCase
Expand Down Expand Up @@ -1466,3 +1466,12 @@ def test_error_if_memmap_impossible(self):
# See https://github.com/astropy/astropy/issues/3766
with fits.open(pth, memmap=True, do_not_scale_image_data=True) as hdul:
hdul[0].data # Just make sure it doesn't crash


def test_deprecated_hdu_classes():
from astropy.io.fits.hdu.base import _ExtensionHDU, _NonstandardExtHDU

with pytest.warns(AstropyDeprecationWarning):
_ExtensionHDU()
with pytest.warns(AstropyDeprecationWarning):
_NonstandardExtHDU()
115 changes: 1 addition & 114 deletions astropy/io/fits/tests/test_table.py
Expand Up @@ -6,7 +6,6 @@
import pickle
import re
import sys
import warnings

import numpy as np
import pytest
Expand All @@ -26,7 +25,7 @@
from astropy.table import Table
from astropy.units import Unit, UnitsWarning, UnrecognizedUnit
from astropy.utils.compat import NUMPY_LT_1_22_1
from astropy.utils.exceptions import AstropyDeprecationWarning, AstropyUserWarning
from astropy.utils.exceptions import AstropyUserWarning

from .conftest import FitsTestCase

Expand Down Expand Up @@ -3786,118 +3785,6 @@ def test_regression_scalar_indexing():
assert all(a == b for a, b in zip(x1a, x1b))


def test_new_column_attributes_preserved(tmp_path):
# Regression test for https://github.com/astropy/astropy/issues/7145
# This makes sure that for now we don't clear away keywords that have
# newly been recognized (in Astropy 3.0) as special column attributes but
# instead just warn that we might do so in future. The new keywords are:
# TCTYP, TCUNI, TCRPX, TCRVL, TCDLT, TRPOS

col = []
col.append(fits.Column(name="TIME", format="1E", unit="s"))
col.append(fits.Column(name="RAWX", format="1I", unit="pixel"))
col.append(fits.Column(name="RAWY", format="1I"))
cd = fits.ColDefs(col)

hdr = fits.Header()

# Keywords that will get ignored in favor of these in the data
hdr["TUNIT1"] = "pixel"
hdr["TUNIT2"] = "m"
hdr["TUNIT3"] = "m"

# Keywords that were added in Astropy 3.0 that should eventually be
# ignored and set on the data instead
hdr["TCTYP2"] = "RA---TAN"
hdr["TCTYP3"] = "ANGLE"
hdr["TCRVL2"] = -999.0
hdr["TCRVL3"] = -999.0
hdr["TCRPX2"] = 1.0
hdr["TCRPX3"] = 1.0
hdr["TALEN2"] = 16384
hdr["TALEN3"] = 1024
hdr["TCUNI2"] = "angstrom"
hdr["TCUNI3"] = "deg"

# Other non-relevant keywords
hdr["RA"] = 1.5
hdr["DEC"] = 3.0

with pytest.warns(AstropyDeprecationWarning) as warning_list:
hdu = fits.BinTableHDU.from_columns(cd, hdr)
assert str(warning_list[0].message).startswith(
"The following keywords are now recognized as special"
)

# First, check that special keywords such as TUNIT are ignored in the header
# We may want to change that behavior in future, but this is the way it's
# been for a while now.

assert hdu.columns[0].unit == "s"
assert hdu.columns[1].unit == "pixel"
assert hdu.columns[2].unit is None

assert hdu.header["TUNIT1"] == "s"
assert hdu.header["TUNIT2"] == "pixel"
assert "TUNIT3" not in hdu.header # TUNIT3 was removed

# Now, check that the new special keywords are actually still there
# but weren't used to set the attributes on the data

assert hdu.columns[0].coord_type is None
assert hdu.columns[1].coord_type is None
assert hdu.columns[2].coord_type is None

assert "TCTYP1" not in hdu.header
assert hdu.header["TCTYP2"] == "RA---TAN"
assert hdu.header["TCTYP3"] == "ANGLE"

# Make sure that other keywords are still there

assert hdu.header["RA"] == 1.5
assert hdu.header["DEC"] == 3.0

# Now we can write this HDU to a file and re-load. Re-loading *should*
# cause the special column attributes to be picked up (it's just that when a
# header is manually specified, these values are ignored)

filename = tmp_path / "test.fits"

hdu.writeto(filename)

# Make sure we don't emit a warning in this case
with warnings.catch_warnings(record=True) as warning_list:
with fits.open(filename) as hdul:
hdu2 = hdul[1]
assert len(warning_list) == 0

# Check that column attributes are now correctly set

assert hdu2.columns[0].unit == "s"
assert hdu2.columns[1].unit == "pixel"
assert hdu2.columns[2].unit is None

assert hdu2.header["TUNIT1"] == "s"
assert hdu2.header["TUNIT2"] == "pixel"
assert "TUNIT3" not in hdu2.header # TUNIT3 was removed

# Now, check that the new special keywords are actually still there
# but weren't used to set the attributes on the data

assert hdu2.columns[0].coord_type is None
assert hdu2.columns[1].coord_type == "RA---TAN"
assert hdu2.columns[2].coord_type == "ANGLE"

assert "TCTYP1" not in hdu2.header
assert hdu2.header["TCTYP2"] == "RA---TAN"
assert hdu2.header["TCTYP3"] == "ANGLE"

# Make sure that other keywords are still there

assert hdu2.header["RA"] == 1.5
assert hdu2.header["DEC"] == 3.0


def test_empty_table(tmp_path):
ofile = tmp_path / "emptytable.fits"
hdu = fits.BinTableHDU(header=None, data=None, name="TEST")
Expand Down
7 changes: 7 additions & 0 deletions docs/changes/io.fits/15396.api.rst
@@ -0,0 +1,7 @@
Deprecate ``_ExtensionHDU`` and ``_NonstandardExtHDU`` (use ``ExtensionHDU`` or
``NonstandardExtHDU`` instead).

Remove special handling of TCTYP TCUNI TCRPX TCRVL TCDLT TRPOS (#7157)

Rename and deprecate ``TableHDU.update`` to ``TableHDU.update_header``, for
consistency with ``ImageHDU``.

0 comments on commit de782c5

Please sign in to comment.