Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Deprecate extra_fits #402

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions docs/source/jwst/datamodels/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -293,25 +293,29 @@ use::
Extra FITS keywords
-------------------

.. warning::

This feature is deprecated and will be removed in a future release.

When loading arbitrary FITS files, there may be keywords that are not
listed in the schema for that data model. These "extra" FITS keywords
are put under the model in the `_extra_fits` namespace.
are put under the model in the `extra_fits` namespace.

Under the `_extra_fits` namespace is a section for each header data
Under the `extra_fits` namespace is a section for each header data
unit, and under those are the extra FITS keywords. For example, if
the FITS file contains a keyword `FOO` in the primary header, its
value can be obtained using::

model._extra_fits.PRIMARY.FOO
model.extra_fits.PRIMARY.FOO

This feature is useful to retain any extra keywords from input files
to output products.

To get a list of everything in `_extra_fits`::
To get a list of everything in `extra_fits`::

model._extra_fits._instance
model.extra_fits._instance

returns a dictionary of of the instance at the model._extra_fits node.
returns a dictionary of of the instance at the model.extra_fits node.

`_instance` can be used at any node in the tree to return a dictionary
of rest of the tree structure at that node.
Expand Down
6 changes: 6 additions & 0 deletions docs/source/jwst/datamodels/structure.rst
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,12 @@ not found in the schema are placed in the header subtree and data is
placed in the data subtree. Finally, it reads the history keywords
and places them in a history structure.

.. note::

Manipulation of `extra_fits` is a deprecated feature. The way
stdatamodels handles FITS keywords that are not in the schema is
likely to change in a future release.

To write a model back to a file, call the save method on the file. It
first calls validate_required to check the schema to see if all the
required fields are present in the model. Then it calls the function
Expand Down
7 changes: 5 additions & 2 deletions src/stdatamodels/jwst/datamodels/tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,8 +202,11 @@ def test_update_from_datamodel(tmp_path, datamodel_for_update, only, extra_fits)
# Verify the fixture returns keywords we expect
assert oldim.meta.telescope == "JWST"
assert oldim.meta.wcsinfo.crval1 == 5
assert oldim.extra_fits.PRIMARY.header == [["FOO", "BAR", ""]]
assert oldim.extra_fits.SCI.header == [["BAZ", "BUZ", ""]]
with pytest.raises(
DeprecationWarning, match="Manipulation of extra_fits is deprecated"
):
assert oldim.extra_fits.PRIMARY.header == [["FOO", "BAR", ""]]
assert oldim.extra_fits.SCI.header == [["BAZ", "BUZ", ""]]

newim.update(oldim, only=only, extra_fits=extra_fits)
newim.save(path)
Expand Down
17 changes: 17 additions & 0 deletions src/stdatamodels/model_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -682,7 +682,24 @@ def shape(self):
self._shape = primary_array.shape
return self._shape

def __getattribute__(self, attr):
if attr in ("extra_fits", "_extra_fits"):
warnings.warn(
"Manipulation of extra_fits is deprecated. "
"This feature will be removed in an upcoming release",
DeprecationWarning,
stacklevel=2,
)
return object.__getattribute__(self, attr)

def __setattr__(self, attr, value):
if attr in ("extra_fits", "_extra_fits"):
warnings.warn(
"Manipulation of extra_fits is deprecated. "
"This feature will be removed in an upcoming release",
DeprecationWarning,
stacklevel=2,
)
if attr in frozenset(("shape", "history", "_extra_fits", "schema")):
object.__setattr__(self, attr, value)
else:
Expand Down
19 changes: 12 additions & 7 deletions tests/test_fits.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,8 @@ def test_extra_fits(tmp_path):
hdul.writeto(file_path, overwrite=True)

with DataModel(file_path) as dm:
assert any(h for h in dm.extra_fits.PRIMARY.header if h == ["FOO", "BAR", ""])
with pytest.raises(DeprecationWarning, match="Manipulation of extra_fits is deprecated"):
assert any(h for h in dm.extra_fits.PRIMARY.header if h == ["FOO", "BAR", ""])


def test_hdu_order(tmp_path):
Expand Down Expand Up @@ -600,19 +601,23 @@ def test_no_asdf_extension_extra_fits(tmp_path):
}

with PureFitsModel((5, 5)) as m:
m.extra_fits = {}
m.extra_fits.instance.update(extra_fits)
assert "ASDF" in m.extra_fits.instance
assert "CHECKSUM" in m.extra_fits.ASDF.header[0]
assert "DATASUM" in m.extra_fits.ASDF.header[1]
with pytest.raises(DeprecationWarning, match="Manipulation of extra_fits is deprecated"):
m.extra_fits = {}
m.extra_fits.instance.update(extra_fits)
assert "ASDF" in m.extra_fits.instance
assert "CHECKSUM" in m.extra_fits.ASDF.header[0]
assert "DATASUM" in m.extra_fits.ASDF.header[1]
m.save(path)

with fits.open(path, memmap=False) as hdulist:
assert "ASDF" not in hdulist

with PureFitsModel(path) as m:
with pytest.raises(AttributeError):
m.extra_fits # noqa: B018
with pytest.raises(
DeprecationWarning, match="Manipulation of extra_fits is deprecated"
):
m.extra_fits # noqa: B018


def test_ndarray_validation(tmp_path):
Expand Down
12 changes: 12 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,15 @@ def find_gen_by_id(object_id):
# many models which would indicate they are difficult to garbage
# collect.
assert len(mids) < 2


def test_extra_fits_deprecation():
m = DataModel()
with pytest.warns(DeprecationWarning):
m.extra_fits = "foo"
with pytest.warns(DeprecationWarning):
m._extra_fits = "bar"
with pytest.warns(DeprecationWarning):
_ = m.extra_fits
with pytest.warns(DeprecationWarning):
_ = m._extra_fits
Loading