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

Remove the target keyword from rdm_open #235

Merged
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
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@

- Clean up overlooked randomness in ``maker_utils`` and tests. [#236]

- Remove the unused ``target`` keyword from ``rdm_open`` and fix the original issue that the
keyword was meant to address; namely, passing a datamodel instance to the constructor for
that datamodel instance should return the instance back with no modifications. [#235]

0.16.1 (2023-06-27)
===================

Expand Down
15 changes: 15 additions & 0 deletions src/roman_datamodels/datamodels/_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,22 @@ def __init_subclass__(cls, **kwargs):
# Add to registry
MODEL_REGISTRY[cls._node_type] = cls

def __new__(cls, init=None, **kwargs):
"""
Handle the case where one passes in an already instantiated version
of the model. In this case the constructor should just directly return
the model.
"""
if init.__class__.__name__ == cls.__name__:
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bad things happen (infinite recursion) if one tries to isinstance checks here.

return init

return super().__new__(cls)

def __init__(self, init=None, **kwargs):
if isinstance(init, self.__class__):
# Due to __new__ above, this is already initialized.
return

self._iscopy = False
self._shape = None
self._instance = None
Expand Down
26 changes: 2 additions & 24 deletions src/roman_datamodels/datamodels/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
__all__ = ["rdm_open"]


def rdm_open(init, memmap=False, target=None, **kwargs):
def rdm_open(init, memmap=False, **kwargs):
"""
Datamodel open/create function.
This function opens a Roman datamodel from an asdf file or generates
Expand All @@ -43,34 +43,19 @@ def rdm_open(init, memmap=False, target=None, **kwargs):
- `DataModel` Roman data model instance
memmap : bool
Open ASDF file binary data using memmap (default: False)
target : `DataModel`
If not None value, the `DataModel` implied by the init argument
must be an instance of the target class. If the init value
is already a data model, and matches the target, the init
value is returned, not copied, as opposed to the case where
the init value is a data model, and target is not supplied,
and the returned value is a copy of the init value.

Returns
-------
`DataModel`
"""
with validate.nuke_validation():
file_to_close = None
if target is not None:
if not issubclass(target, DataModel):
raise ValueError("Target must be a subclass of DataModel")
# Temp fix to catch JWST args before being passed to asdf open
if "asn_n_members" in kwargs:
del kwargs["asn_n_members"]
if isinstance(init, asdf.AsdfFile):
asdffile = init
elif isinstance(init, DataModel):
if target is not None:
if not isinstance(init, target):
raise ValueError("First argument is not an instance of target")
else:
return init
# Copy the object so it knows not to close here
return init.copy()
else:
Expand All @@ -86,13 +71,6 @@ def rdm_open(init, memmap=False, target=None, **kwargs):
raise TypeError("Roman datamodels does not accept FITS files or objects")
modeltype = type(asdffile.tree["roman"])
if modeltype in MODEL_REGISTRY:
rmodel = MODEL_REGISTRY[modeltype](asdffile, **kwargs)
if target is not None:
if not issubclass(rmodel.__class__, target):
if file_to_close is not None:
file_to_close.close()
raise ValueError("Referenced ASDF file model type is not subclass of target")
else:
return rmodel
return MODEL_REGISTRY[modeltype](asdffile, **kwargs)
else:
return DataModel(asdffile, **kwargs)
27 changes: 27 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -691,3 +691,30 @@ def test_ramp_from_science_raw():

else:
raise ValueError(f"Unexpected type {type(ramp_value)}, {key}")


@pytest.mark.parametrize("model", datamodels.MODEL_REGISTRY.values())
@pytest.mark.filterwarnings("ignore:This function assumes shape is 2D")
@pytest.mark.filterwarnings("ignore:Input shape must be 5D")
def test_datamodel_construct_like_from_like(model):
"""
This is a regression test for the issue reported issue #51.

Namely, if one passes a datamodel instance to the constructor for the datamodel
of the same type as the instance, an error should not be raised (#51 reports an
error being raised).

Based on the discussion in PR #52, this should return exactly the same instance object
that was passed to the constructor. i.e. it should not return a copy of the instance.
"""

# Create a model
mdl = utils.mk_datamodel(model, shape=(2, 8, 8))

# Modify _iscopy as it will be reset to False by initializer if called incorrectly
mdl._iscopy = "foo"

# Pass model instance to model constructor
new_mdl = model(mdl)
assert new_mdl is mdl
assert new_mdl._iscopy == "foo" # Verify that the constructor didn't override stuff