From 7b78fb2bbe71f999cc4b181cc6a83ad7f81630ad Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Tue, 11 Jul 2023 07:41:46 -0400 Subject: [PATCH 01/22] RCAL-596 Inital changes for reading associations --- src/roman_datamodels/datamodels/_utils.py | 11 ++++- src/roman_datamodels/filetype.py | 56 +++++++++++++++++++++++ 2 files changed, 65 insertions(+), 2 deletions(-) create mode 100644 src/roman_datamodels/filetype.py diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 83169da0..218c047d 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -3,11 +3,13 @@ the open/factory function for creating datamodels """ import warnings +import pdb import asdf import packaging.version -from roman_datamodels import validate +from roman_datamodels import validate, filetype +from romancal.datamodels import ModelContainer from ._core import MODEL_REGISTRY, DataModel @@ -31,7 +33,7 @@ def rdm_open(init, memmap=False, target=None, **kwargs): """ Datamodel open/create function. - This function opens a Roman datamodel from an asdf file or generates + This function opens a Roman datamodel from various files or generates the datamodel from an existing one. Parameters @@ -57,6 +59,7 @@ def rdm_open(init, memmap=False, target=None, **kwargs): """ with validate.nuke_validation(): file_to_close = None + input_file_type = filetype.check(init) if target is not None: if not issubclass(target, DataModel): raise ValueError("Target must be a subclass of DataModel") @@ -73,6 +76,10 @@ def rdm_open(init, memmap=False, target=None, **kwargs): return init # Copy the object so it knows not to close here return init.copy() + elif input_file_type =='asn': + asn_model = ModelContainer(init, save_open=False, return_open=False) + # Copy the object so it knows not to close here + return asn_model else: try: kwargs["copy_arrays"] = not memmap diff --git a/src/roman_datamodels/filetype.py b/src/roman_datamodels/filetype.py new file mode 100644 index 00000000..9cc8a4f6 --- /dev/null +++ b/src/roman_datamodels/filetype.py @@ -0,0 +1,56 @@ +import io +import os +from pathlib import Path +from typing import Union + + +def check(init: Union[os.PathLike, Path, io.FileIO]) -> str: + """ + Determine the type of a file and return it as a string + + Parameters + ---------- + + init : str + file path or file object + + Returns + ------- + file_type: str + a string with the file type ("asdf" or "asn") + + """ + + supported = ("asdf", "json") + + if isinstance(init, (str, os.PathLike, Path)): + path, ext = os.path.splitext(init) + ext = ext.strip(".") + + if not ext: + raise ValueError(f"Input file path does not have an extension: {init}") + + if ext not in supported: # Could be the file is zipped; try splitting again + path, ext = os.path.splitext(path) + ext = ext.strip(".") + + if ext not in supported: + raise ValueError(f"Unrecognized file type for: {init}") + + if ext == "json": # Assume json input is an association + return "asn" + + return ext + elif hasattr(init, "read") and hasattr(init, "seek"): + magic = init.read(5) + init.seek(0, 0) + + if not magic or len(magic) < 5: + raise ValueError(f"Cannot get file type of {str(init)}") + + if magic == b"#ASDF": + return "asdf" + + return "asn" + else: + raise ValueError(f"Cannot get file type of {str(init)}") From 68628c8e2de1f3ec08788ac03dabfe66b983e886 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Tue, 18 Jul 2023 10:49:32 -0400 Subject: [PATCH 02/22] rcal-596 Updates for association processing --- src/roman_datamodels/datamodels/_utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 218c047d..9500c2c0 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -3,7 +3,6 @@ the open/factory function for creating datamodels """ import warnings -import pdb import asdf import packaging.version @@ -59,7 +58,12 @@ def rdm_open(init, memmap=False, target=None, **kwargs): """ with validate.nuke_validation(): file_to_close = None - input_file_type = filetype.check(init) + if isinstance(init, str): + input_file_type = filetype.check(init) + if input_file_type =='asn': + #asn_model = ModelContainer(init, save_open=False, return_open=False) + print("returning a asn string:", init) + return if target is not None: if not issubclass(target, DataModel): raise ValueError("Target must be a subclass of DataModel") @@ -76,10 +80,7 @@ def rdm_open(init, memmap=False, target=None, **kwargs): return init # Copy the object so it knows not to close here return init.copy() - elif input_file_type =='asn': - asn_model = ModelContainer(init, save_open=False, return_open=False) # Copy the object so it knows not to close here - return asn_model else: try: kwargs["copy_arrays"] = not memmap From abc7976a3aa47a41edcd875e4a250aaad54517e0 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Tue, 18 Jul 2023 11:03:51 -0400 Subject: [PATCH 03/22] rcal-596 Updates for association processing --- CHANGES.rst | 2 ++ src/roman_datamodels/datamodels/_utils.py | 2 -- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index 7edf4430..b2d4f0d9 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,5 +1,7 @@ 0.17.0 (unreleased) =================== +- Add check for filetypes for association processing [#241] + - Remove the ``random_utils`` module and make ``maker_utils`` entirely deterministic. [#217] - Add tests to ensure consistency between file-level schemas in RAD and the corresponding diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 9500c2c0..59f127c0 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -8,7 +8,6 @@ import packaging.version from roman_datamodels import validate, filetype -from romancal.datamodels import ModelContainer from ._core import MODEL_REGISTRY, DataModel @@ -61,7 +60,6 @@ def rdm_open(init, memmap=False, target=None, **kwargs): if isinstance(init, str): input_file_type = filetype.check(init) if input_file_type =='asn': - #asn_model = ModelContainer(init, save_open=False, return_open=False) print("returning a asn string:", init) return if target is not None: From 094cccabefe69363cddb272646dbe47360143a66 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Tue, 18 Jul 2023 14:33:15 -0400 Subject: [PATCH 04/22] RCAL-596 Added tests for filetype --- tests/data/empty.asdf | 0 tests/data/empty.json | 0 tests/data/example_schema.json | 43 +++++++++++++++++++++++++++++++++ tests/data/fake.asdf | 1 + tests/data/fake.json | 1 + tests/data/pluto.asdf | Bin 0 -> 805623 bytes tests/test_filetype.py | 41 +++++++++++++++++++++++++++++++ 7 files changed, 86 insertions(+) create mode 100644 tests/data/empty.asdf create mode 100644 tests/data/empty.json create mode 100755 tests/data/example_schema.json create mode 100644 tests/data/fake.asdf create mode 100644 tests/data/fake.json create mode 100644 tests/data/pluto.asdf create mode 100644 tests/test_filetype.py diff --git a/tests/data/empty.asdf b/tests/data/empty.asdf new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/empty.json b/tests/data/empty.json new file mode 100644 index 00000000..e69de29b diff --git a/tests/data/example_schema.json b/tests/data/example_schema.json new file mode 100755 index 00000000..febac841 --- /dev/null +++ b/tests/data/example_schema.json @@ -0,0 +1,43 @@ +{ + "date" : { + "title" : "[yyyy-mm-ddThh:mm:ss.ssssss] UTC date file created", + "type" : "string", + "sql_dtype" : "datetime2", + "fits_keyword" : "DATE", + "description" : "The UTC date and time when the HDU was created, in the form YYYY-MM-DDThh:mm:ss.ssssss, where YYYY shall be the four-digit calendar year number, MM the two-digit month number with January given by 01 and December by 12, and DD the two-digit day of the month. The literal T shall separate the date and time, hh shall be the two-digit hour in the day, mm the two-digit number of minutes after the hour, and ss.ssssss the number of seconds (two digits followed by a fraction accurate to microseconds) after the minute. Default values must not be given to any portion of the date/time string, and leading zeros must not be omitted.", + "calculation" : "Operating system time in the format of YYYY-MM-DDThh:mm:ss.ssssss", + "default_value" : "", + "example" : "2015-01-01T00:00:00.000001", + "units" : "", + "sw_source" : "calculation", + "source" : "Science Data Processing (SDP)", + "destination" : ["ScienceCommon.date","GuideStar.date"], + "level" : "1a", + "si" : "Multiple", + "section" : "Basic", + "mode" : "All", + "fits_hdu" : "PRIMARY", + "misc" : "" + }, + + "origin" : { + "title" : "institution responsible for creating FITS file", + "type" : "string", + "sql_dtype" : "nvarchar(20)", + "fits_keyword" : "ORIGIN", + "description" : "Identifies the organization or institution responsible for creating the FITS file.", + "calculation" : "", + "default_value" : "STSCI", + "example" : "STSCI", + "units" : "", + "sw_source" : "", + "source" : "Science Data Processing (SDP)", + "destination" : ["ScienceCommon.origin","GuideStar.origin"], + "level" : "1a", + "si" : "Multiple", + "section" : "Basic", + "mode" : "All", + "fits_hdu" : "PRIMARY", + "misc" : "" + } +} diff --git a/tests/data/fake.asdf b/tests/data/fake.asdf new file mode 100644 index 00000000..5851b8d2 --- /dev/null +++ b/tests/data/fake.asdf @@ -0,0 +1 @@ +not actually an ASDF file \ No newline at end of file diff --git a/tests/data/fake.json b/tests/data/fake.json new file mode 100644 index 00000000..ed4e52fc --- /dev/null +++ b/tests/data/fake.json @@ -0,0 +1 @@ +not actually a JSON file diff --git a/tests/data/pluto.asdf b/tests/data/pluto.asdf new file mode 100644 index 0000000000000000000000000000000000000000..c21b4cc3da1db72283f2cfa3cfe2aaf578e7f834 GIT binary patch literal 805623 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 BFOdKM literal 0 HcmV?d00001 diff --git a/tests/test_filetype.py b/tests/test_filetype.py new file mode 100644 index 00000000..a439e6c1 --- /dev/null +++ b/tests/test_filetype.py @@ -0,0 +1,41 @@ +from pathlib import Path + +import pytest + +from roman_datamodels import filetype + +DATA_DIRECTORY = Path(__file__).parent / "data" + + +def test_filetype(): + file_1 = filetype.check(DATA_DIRECTORY / "empty.json") + file_2 = filetype.check(DATA_DIRECTORY / "example_schema.json") + with open(DATA_DIRECTORY / "fake.json") as file_h: + file_3 = filetype.check(file_h) + file_4 = filetype.check(DATA_DIRECTORY / "empty.asdf") + file_5 = filetype.check(DATA_DIRECTORY / "pluto.asdf") + with open(DATA_DIRECTORY / "pluto.asdf", "rb") as file_h: + file_6 = filetype.check(file_h) + file_7 = filetype.check(DATA_DIRECTORY / "fake.asdf") + with open(DATA_DIRECTORY / "fake.json") as file_h: + file_8 = filetype.check(file_h) + file_9 = filetype.check(str(DATA_DIRECTORY / "pluto.asdf")) + + assert file_1 == "asn" + assert file_2 == "asn" + assert file_3 == "asn" + assert file_4 == "asdf" + assert file_5 == "asdf" + assert file_6 == "asdf" + assert file_7 == "asdf" + assert file_8 == "asn" + assert file_9 == "asdf" + + with pytest.raises(ValueError): + filetype.check(DATA_DIRECTORY / "empty.txt") + + with pytest.raises(ValueError): + filetype.check(2) + + with pytest.raises(ValueError): + filetype.check("test") From 45815ab9d965ac11fdc537669ea325d6c5d79a2c Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Wed, 19 Jul 2023 09:51:31 -0400 Subject: [PATCH 05/22] rcal-596 Incorporate/rebase _utils updates. --- src/roman_datamodels/datamodels/_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 59f127c0..456601ab 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -60,8 +60,8 @@ def rdm_open(init, memmap=False, target=None, **kwargs): if isinstance(init, str): input_file_type = filetype.check(init) if input_file_type =='asn': - print("returning a asn string:", init) - return + print("Returning an asn string:", init) + return init if target is not None: if not issubclass(target, DataModel): raise ValueError("Target must be a subclass of DataModel") From 63c400fa6ca6b3b9fca2c914175e887fb3e3c3f0 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Thu, 20 Jul 2023 08:37:43 -0400 Subject: [PATCH 06/22] change log msg --- src/roman_datamodels/datamodels/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 456601ab..4a037274 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -60,7 +60,7 @@ def rdm_open(init, memmap=False, target=None, **kwargs): if isinstance(init, str): input_file_type = filetype.check(init) if input_file_type =='asn': - print("Returning an asn string:", init) + print(f"Returning an asn string: {init}") return init if target is not None: if not issubclass(target, DataModel): From acd8d4d6eb641f8dc714d2004b580d1c197aecd0 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Thu, 20 Jul 2023 09:52:17 -0400 Subject: [PATCH 07/22] RCAL-596 Fix my rebase of _utils --- src/roman_datamodels/datamodels/_utils.py | 100 +++++++++++----------- 1 file changed, 49 insertions(+), 51 deletions(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 4a037274..e2042771 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -5,7 +5,7 @@ import warnings import asdf -import packaging.version +from astropy.utils import minversion from roman_datamodels import validate, filetype @@ -13,7 +13,9 @@ # .dev is included in the version comparison to allow for correct version # comparisons with development versions of asdf 3.0 -if packaging.version.Version(asdf.__version__) < packaging.version.Version("3.dev"): +if minversion(asdf, "3.dev"): + AsdfInFits = None +else: with warnings.catch_warnings(): warnings.filterwarnings( "ignore", @@ -21,17 +23,47 @@ message=r"AsdfInFits has been deprecated.*", ) from asdf.fits_embed import AsdfInFits -else: - AsdfInFits = None __all__ = ["rdm_open"] -def rdm_open(init, memmap=False, target=None, **kwargs): +def _open_path_like(init, memmap=False, **kwargs): + """ + Attempt to open init as if it was a path-like object. + + Parameters + ---------- + init : str + Any path-like object that can be opened by asdf such as a valid string + memmap : bool + If we should open the file with memmap + **kwargs: + Any additional arguments to pass to asdf.open + + Returns + ------- + `asdf.AsdfFile` + """ + kwargs["copy_arrays"] = not memmap + + try: + asdf_file = asdf.open(init, **kwargs) + except ValueError as err: + raise TypeError("Open requires a filepath, file-like object, or Roman datamodel") from err + + # This is only needed until we move min asdf version to 3.0 + if AsdfInFits is not None and isinstance(asdf_file, AsdfInFits): + asdf_file.close() + raise TypeError("Roman datamodels does not accept FITS files or objects") + + return asdf_file + + +def rdm_open(init, memmap=False, **kwargs): """ Datamodel open/create function. - This function opens a Roman datamodel from various files or generates + This function opens a Roman datamodel from an asdf file or generates the datamodel from an existing one. Parameters @@ -43,62 +75,28 @@ 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 isinstance(init, str): input_file_type = filetype.check(init) if input_file_type =='asn': print(f"Returning an asn string: {init}") return init - if target is not None: - if not issubclass(target, DataModel): - raise ValueError("Target must be a subclass of DataModel") + if isinstance(init, DataModel): + # Copy the object so it knows not to close here + return init.copy() + # 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() - # Copy the object so it knows not to close here - else: - try: - kwargs["copy_arrays"] = not memmap - asdffile = asdf.open(init, **kwargs) - file_to_close = asdffile - except ValueError: - raise TypeError("Open requires a filepath, file-like object, or Roman datamodel") - if AsdfInFits is not None and isinstance(asdffile, AsdfInFits): - if file_to_close is not None: - file_to_close.close() - 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 - else: - return DataModel(asdffile, **kwargs) + + asdf_file = init if isinstance(init, asdf.AsdfFile) else _open_path_like(init, memmap=memmap, **kwargs) + if (model_type := type(asdf_file.tree["roman"])) in MODEL_REGISTRY: + return MODEL_REGISTRY[model_type](asdf_file, **kwargs) + + asdf_file.close() + raise TypeError(f"Unknown datamodel type: {model_type}") From 5ccc842d009d66c3cffc51ee8c2bafa1e244821c Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Thu, 20 Jul 2023 09:56:32 -0400 Subject: [PATCH 08/22] RCAL-596 Fix my rebase of _utils --- src/roman_datamodels/datamodels/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index e2042771..ddfe33ce 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -85,7 +85,7 @@ def rdm_open(init, memmap=False, **kwargs): input_file_type = filetype.check(init) if input_file_type =='asn': print(f"Returning an asn string: {init}") - return init + return init.copy() if isinstance(init, DataModel): # Copy the object so it knows not to close here return init.copy() From 937b0ac6e70b4d737e8dfc5814deab4046c28800 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Thu, 20 Jul 2023 09:59:48 -0400 Subject: [PATCH 09/22] RCAL-596 Fix my rebase of _utils --- src/roman_datamodels/datamodels/_utils.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index ddfe33ce..6eeca8f5 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -82,10 +82,11 @@ def rdm_open(init, memmap=False, **kwargs): """ with validate.nuke_validation(): if isinstance(init, str): - input_file_type = filetype.check(init) - if input_file_type =='asn': - print(f"Returning an asn string: {init}") - return init.copy() + input_file_type = filetype.check(init) + if input_file_type =='asn': + print(f"Returning an asn string: {init}") + return init.copy() + if isinstance(init, DataModel): # Copy the object so it knows not to close here return init.copy() From 05df2a3b4b965ac84517a507e834224e386e643a Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Thu, 20 Jul 2023 10:02:46 -0400 Subject: [PATCH 10/22] RCAL-596 Fix my rebase of _utils --- src/roman_datamodels/datamodels/_utils.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 6eeca8f5..16830a6d 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -82,10 +82,10 @@ def rdm_open(init, memmap=False, **kwargs): """ with validate.nuke_validation(): if isinstance(init, str): - input_file_type = filetype.check(init) - if input_file_type =='asn': - print(f"Returning an asn string: {init}") - return init.copy() + input_file_type = filetype.check(init) + if input_file_type =='asn': + print(f"Returning an asn string: {init}") + return init.copy() if isinstance(init, DataModel): # Copy the object so it knows not to close here From a1b141b2822bdda03100ba55ec6682ddfc4b3c76 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Thu, 20 Jul 2023 10:32:29 -0400 Subject: [PATCH 11/22] RCAL-596 Reorder asn test in _utils --- src/roman_datamodels/datamodels/_utils.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 16830a6d..f4a39aab 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -80,12 +80,13 @@ def rdm_open(init, memmap=False, **kwargs): ------- `DataModel` """ + if isinstance(init, str): + input_file_type = filetype.check(init) + if input_file_type =='asn': + print(f"Returning an asn string: {init}") + return init + with validate.nuke_validation(): - if isinstance(init, str): - input_file_type = filetype.check(init) - if input_file_type =='asn': - print(f"Returning an asn string: {init}") - return init.copy() if isinstance(init, DataModel): # Copy the object so it knows not to close here From a1115e7827f2404c4767f3388b1d44542224eb39 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 20 Jul 2023 15:50:44 +0000 Subject: [PATCH 12/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/roman_datamodels/datamodels/_utils.py | 6 +++--- tests/test_filetype.py | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index e696aae8..fd44dac1 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -7,7 +7,7 @@ import asdf from astropy.utils import minversion -from roman_datamodels import validate, filetype +from roman_datamodels import filetype, validate from ._core import MODEL_REGISTRY, DataModel @@ -82,10 +82,10 @@ def rdm_open(init, memmap=False, **kwargs): """ if isinstance(init, str): input_file_type = filetype.check(init) - if input_file_type =='asn': + if input_file_type == "asn": print(f"Returning an asn string: {init}") return init - + with validate.nuke_validation(): if isinstance(init, DataModel): # Copy the object so it knows not to close here diff --git a/tests/test_filetype.py b/tests/test_filetype.py index a439e6c1..0e194aba 100644 --- a/tests/test_filetype.py +++ b/tests/test_filetype.py @@ -10,14 +10,14 @@ def test_filetype(): file_1 = filetype.check(DATA_DIRECTORY / "empty.json") file_2 = filetype.check(DATA_DIRECTORY / "example_schema.json") - with open(DATA_DIRECTORY / "fake.json") as file_h: + with open(DATA_DIRECTORY / "fake.json") as file_h: file_3 = filetype.check(file_h) file_4 = filetype.check(DATA_DIRECTORY / "empty.asdf") file_5 = filetype.check(DATA_DIRECTORY / "pluto.asdf") - with open(DATA_DIRECTORY / "pluto.asdf", "rb") as file_h: + with open(DATA_DIRECTORY / "pluto.asdf", "rb") as file_h: file_6 = filetype.check(file_h) file_7 = filetype.check(DATA_DIRECTORY / "fake.asdf") - with open(DATA_DIRECTORY / "fake.json") as file_h: + with open(DATA_DIRECTORY / "fake.json") as file_h: file_8 = filetype.check(file_h) file_9 = filetype.check(str(DATA_DIRECTORY / "pluto.asdf")) From e283ad1445cf654c8ca3f7fdb77e2ed012ec693c Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Thu, 20 Jul 2023 15:58:37 -0400 Subject: [PATCH 13/22] RCAL-596 code cleanup for review --- src/roman_datamodels/datamodels/_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index fd44dac1..41254245 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -83,7 +83,6 @@ def rdm_open(init, memmap=False, **kwargs): if isinstance(init, str): input_file_type = filetype.check(init) if input_file_type == "asn": - print(f"Returning an asn string: {init}") return init with validate.nuke_validation(): From eecddc29482bac193cf0ee1c57e8c738e2ba9309 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Fri, 21 Jul 2023 08:58:25 -0400 Subject: [PATCH 14/22] Update utils --- src/roman_datamodels/datamodels/_utils.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 41254245..efccabc2 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -7,7 +7,7 @@ import asdf from astropy.utils import minversion -from roman_datamodels import filetype, validate +from roman_datamodels import validate from ._core import MODEL_REGISTRY, DataModel @@ -80,11 +80,6 @@ def rdm_open(init, memmap=False, **kwargs): ------- `DataModel` """ - if isinstance(init, str): - input_file_type = filetype.check(init) - if input_file_type == "asn": - return init - with validate.nuke_validation(): if isinstance(init, DataModel): # Copy the object so it knows not to close here From d2c912d1546621799d8fb24a8320cca5f9abcf44 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Fri, 21 Jul 2023 08:58:52 -0400 Subject: [PATCH 15/22] Update utils --- src/roman_datamodels/datamodels/_utils.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index efccabc2..41254245 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -7,7 +7,7 @@ import asdf from astropy.utils import minversion -from roman_datamodels import validate +from roman_datamodels import filetype, validate from ._core import MODEL_REGISTRY, DataModel @@ -80,6 +80,11 @@ def rdm_open(init, memmap=False, **kwargs): ------- `DataModel` """ + if isinstance(init, str): + input_file_type = filetype.check(init) + if input_file_type == "asn": + return init + with validate.nuke_validation(): if isinstance(init, DataModel): # Copy the object so it knows not to close here From 786960ea98b8b920057621970f58399955ade95d Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Mon, 24 Jul 2023 12:29:32 -0400 Subject: [PATCH 16/22] RCAL-596 pseudo review updates --- src/roman_datamodels/datamodels/_utils.py | 17 ++++++--- tests/data/empty.asdf | 0 tests/data/empty.json | 0 tests/data/example_schema.json | 43 ---------------------- tests/data/fake.asdf | 1 - tests/data/fake.json | 1 - tests/data/pluto.asdf | Bin 805623 -> 0 bytes tests/test_filetype.py | 41 --------------------- 8 files changed, 11 insertions(+), 92 deletions(-) delete mode 100644 tests/data/empty.asdf delete mode 100644 tests/data/empty.json delete mode 100755 tests/data/example_schema.json delete mode 100644 tests/data/fake.asdf delete mode 100644 tests/data/fake.json delete mode 100644 tests/data/pluto.asdf delete mode 100644 tests/test_filetype.py diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 41254245..1fad5383 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -7,7 +7,7 @@ import asdf from astropy.utils import minversion -from roman_datamodels import filetype, validate +from roman_datamodels import validate from ._core import MODEL_REGISTRY, DataModel @@ -80,20 +80,25 @@ def rdm_open(init, memmap=False, **kwargs): ------- `DataModel` """ - if isinstance(init, str): - input_file_type = filetype.check(init) - if input_file_type == "asn": - return init with validate.nuke_validation(): if isinstance(init, DataModel): # Copy the object so it knows not to close here return init.copy() + if isinstance(init, str): + exts = Path(init).suffixes + if not exts: + raise ValueError(f"Input file path does not have an extension: {init}") + + # Assume json files are asn and return them + if exts[0] == "json": + return init + # Temp fix to catch JWST args before being passed to asdf open if "asn_n_members" in kwargs: del kwargs["asn_n_members"] - + asdf_file = init if isinstance(init, asdf.AsdfFile) else _open_path_like(init, memmap=memmap, **kwargs) if (model_type := type(asdf_file.tree["roman"])) in MODEL_REGISTRY: return MODEL_REGISTRY[model_type](asdf_file, **kwargs) diff --git a/tests/data/empty.asdf b/tests/data/empty.asdf deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/data/empty.json b/tests/data/empty.json deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/data/example_schema.json b/tests/data/example_schema.json deleted file mode 100755 index febac841..00000000 --- a/tests/data/example_schema.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "date" : { - "title" : "[yyyy-mm-ddThh:mm:ss.ssssss] UTC date file created", - "type" : "string", - "sql_dtype" : "datetime2", - "fits_keyword" : "DATE", - "description" : "The UTC date and time when the HDU was created, in the form YYYY-MM-DDThh:mm:ss.ssssss, where YYYY shall be the four-digit calendar year number, MM the two-digit month number with January given by 01 and December by 12, and DD the two-digit day of the month. The literal T shall separate the date and time, hh shall be the two-digit hour in the day, mm the two-digit number of minutes after the hour, and ss.ssssss the number of seconds (two digits followed by a fraction accurate to microseconds) after the minute. Default values must not be given to any portion of the date/time string, and leading zeros must not be omitted.", - "calculation" : "Operating system time in the format of YYYY-MM-DDThh:mm:ss.ssssss", - "default_value" : "", - "example" : "2015-01-01T00:00:00.000001", - "units" : "", - "sw_source" : "calculation", - "source" : "Science Data Processing (SDP)", - "destination" : ["ScienceCommon.date","GuideStar.date"], - "level" : "1a", - "si" : "Multiple", - "section" : "Basic", - "mode" : "All", - "fits_hdu" : "PRIMARY", - "misc" : "" - }, - - "origin" : { - "title" : "institution responsible for creating FITS file", - "type" : "string", - "sql_dtype" : "nvarchar(20)", - "fits_keyword" : "ORIGIN", - "description" : "Identifies the organization or institution responsible for creating the FITS file.", - "calculation" : "", - "default_value" : "STSCI", - "example" : "STSCI", - "units" : "", - "sw_source" : "", - "source" : "Science Data Processing (SDP)", - "destination" : ["ScienceCommon.origin","GuideStar.origin"], - "level" : "1a", - "si" : "Multiple", - "section" : "Basic", - "mode" : "All", - "fits_hdu" : "PRIMARY", - "misc" : "" - } -} diff --git a/tests/data/fake.asdf b/tests/data/fake.asdf deleted file mode 100644 index 5851b8d2..00000000 --- a/tests/data/fake.asdf +++ /dev/null @@ -1 +0,0 @@ -not actually an ASDF file \ No newline at end of file diff --git a/tests/data/fake.json b/tests/data/fake.json deleted file mode 100644 index ed4e52fc..00000000 --- a/tests/data/fake.json +++ /dev/null @@ -1 +0,0 @@ -not actually a JSON file diff --git a/tests/data/pluto.asdf b/tests/data/pluto.asdf deleted file mode 100644 index c21b4cc3da1db72283f2cfa3cfe2aaf578e7f834..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 805623 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 BFOdKM diff --git a/tests/test_filetype.py b/tests/test_filetype.py deleted file mode 100644 index 0e194aba..00000000 --- a/tests/test_filetype.py +++ /dev/null @@ -1,41 +0,0 @@ -from pathlib import Path - -import pytest - -from roman_datamodels import filetype - -DATA_DIRECTORY = Path(__file__).parent / "data" - - -def test_filetype(): - file_1 = filetype.check(DATA_DIRECTORY / "empty.json") - file_2 = filetype.check(DATA_DIRECTORY / "example_schema.json") - with open(DATA_DIRECTORY / "fake.json") as file_h: - file_3 = filetype.check(file_h) - file_4 = filetype.check(DATA_DIRECTORY / "empty.asdf") - file_5 = filetype.check(DATA_DIRECTORY / "pluto.asdf") - with open(DATA_DIRECTORY / "pluto.asdf", "rb") as file_h: - file_6 = filetype.check(file_h) - file_7 = filetype.check(DATA_DIRECTORY / "fake.asdf") - with open(DATA_DIRECTORY / "fake.json") as file_h: - file_8 = filetype.check(file_h) - file_9 = filetype.check(str(DATA_DIRECTORY / "pluto.asdf")) - - assert file_1 == "asn" - assert file_2 == "asn" - assert file_3 == "asn" - assert file_4 == "asdf" - assert file_5 == "asdf" - assert file_6 == "asdf" - assert file_7 == "asdf" - assert file_8 == "asn" - assert file_9 == "asdf" - - with pytest.raises(ValueError): - filetype.check(DATA_DIRECTORY / "empty.txt") - - with pytest.raises(ValueError): - filetype.check(2) - - with pytest.raises(ValueError): - filetype.check("test") From 0d048a6db5b205b71cf6c9d9680449a4255df65a Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Mon, 24 Jul 2023 13:48:26 -0400 Subject: [PATCH 17/22] RCAL-596 import Path from pathlib --- src/roman_datamodels/datamodels/_utils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 1fad5383..c1f3825b 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -5,6 +5,7 @@ import warnings import asdf +from pathlib import Path from astropy.utils import minversion from roman_datamodels import validate From f3df2fff306748a3b6389b32eb1478222d995cb4 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Tue, 25 Jul 2023 14:39:42 -0400 Subject: [PATCH 18/22] rcal-596 review updates --- src/roman_datamodels/datamodels/_utils.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index c1f3825b..4696631f 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -81,11 +81,9 @@ def rdm_open(init, memmap=False, **kwargs): ------- `DataModel` """ - - with validate.nuke_validation(): if isinstance(init, DataModel): # Copy the object so it knows not to close here - return init.copy() + return init.copy(deepcopy=False) if isinstance(init, str): exts = Path(init).suffixes @@ -99,7 +97,7 @@ def rdm_open(init, memmap=False, **kwargs): # Temp fix to catch JWST args before being passed to asdf open if "asn_n_members" in kwargs: del kwargs["asn_n_members"] - + asdf_file = init if isinstance(init, asdf.AsdfFile) else _open_path_like(init, memmap=memmap, **kwargs) if (model_type := type(asdf_file.tree["roman"])) in MODEL_REGISTRY: return MODEL_REGISTRY[model_type](asdf_file, **kwargs) From 3974ac6a03ff6e86016c55cc0da8bd9d614aff5c Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Tue, 25 Jul 2023 15:34:04 -0400 Subject: [PATCH 19/22] rcal-596 updates for test_stnode --- src/roman_datamodels/datamodels/_utils.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index 4696631f..d2dccaab 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -5,7 +5,6 @@ import warnings import asdf -from pathlib import Path from astropy.utils import minversion from roman_datamodels import validate @@ -81,10 +80,19 @@ def rdm_open(init, memmap=False, **kwargs): ------- `DataModel` """ + with validate.nuke_validation(): if isinstance(init, DataModel): # Copy the object so it knows not to close here return init.copy(deepcopy=False) + # Temp fix to catch JWST args before being passed to asdf open + if "asn_n_members" in kwargs: + del kwargs["asn_n_members"] + + asdf_file = init if isinstance(init, asdf.AsdfFile) else _open_path_like(init, memmap=memmap, **kwargs) + if (model_type := type(asdf_file.tree["roman"])) in MODEL_REGISTRY: + return MODEL_REGISTRY[model_type](asdf_file, **kwargs) + if isinstance(init, str): exts = Path(init).suffixes if not exts: @@ -94,13 +102,5 @@ def rdm_open(init, memmap=False, **kwargs): if exts[0] == "json": return init - # Temp fix to catch JWST args before being passed to asdf open - if "asn_n_members" in kwargs: - del kwargs["asn_n_members"] - - asdf_file = init if isinstance(init, asdf.AsdfFile) else _open_path_like(init, memmap=memmap, **kwargs) - if (model_type := type(asdf_file.tree["roman"])) in MODEL_REGISTRY: - return MODEL_REGISTRY[model_type](asdf_file, **kwargs) - asdf_file.close() raise TypeError(f"Unknown datamodel type: {model_type}") From 6538574dcd6ff58d4689fea8b3fd4860edf58682 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Wed, 26 Jul 2023 11:38:40 -0400 Subject: [PATCH 20/22] rcal-596 Updates --- src/roman_datamodels/datamodels/_core.py | 8 ++-- src/roman_datamodels/datamodels/_utils.py | 1 + src/roman_datamodels/filetype.py | 56 ----------------------- 3 files changed, 5 insertions(+), 60 deletions(-) delete mode 100644 src/roman_datamodels/filetype.py diff --git a/src/roman_datamodels/datamodels/_core.py b/src/roman_datamodels/datamodels/_core.py index 04ae3ea6..8e8f8ae8 100644 --- a/src/roman_datamodels/datamodels/_core.py +++ b/src/roman_datamodels/datamodels/_core.py @@ -137,9 +137,9 @@ def __del__(self): """Ensure closure of resources when deleted.""" self.close() - def copy(self, memo=None): + def copy(self, deepcopy=True, memo=None): result = self.__class__(init=None) - self.clone(result, self, deepcopy=True, memo=memo) + self.clone(result, self, deepcopy=deepcopy, memo=memo) return result __copy__ = __deepcopy__ = copy @@ -184,9 +184,9 @@ def save(self, path, dir_path=None, *args, **kwargs): def open_asdf(self, init=None, **kwargs): with validate.nuke_validation(): if isinstance(init, str): - asdffile = asdf.open(init) + asdffile = asdf.open(init, **kwargs) else: - asdffile = asdf.AsdfFile(init) + asdffile = asdf.AsdfFile(init, **kwargs) return asdffile def to_asdf(self, init, *args, **kwargs): diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index d2dccaab..a9bb6f2e 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -6,6 +6,7 @@ import asdf from astropy.utils import minversion +from pathlib import Path from roman_datamodels import validate diff --git a/src/roman_datamodels/filetype.py b/src/roman_datamodels/filetype.py deleted file mode 100644 index 9cc8a4f6..00000000 --- a/src/roman_datamodels/filetype.py +++ /dev/null @@ -1,56 +0,0 @@ -import io -import os -from pathlib import Path -from typing import Union - - -def check(init: Union[os.PathLike, Path, io.FileIO]) -> str: - """ - Determine the type of a file and return it as a string - - Parameters - ---------- - - init : str - file path or file object - - Returns - ------- - file_type: str - a string with the file type ("asdf" or "asn") - - """ - - supported = ("asdf", "json") - - if isinstance(init, (str, os.PathLike, Path)): - path, ext = os.path.splitext(init) - ext = ext.strip(".") - - if not ext: - raise ValueError(f"Input file path does not have an extension: {init}") - - if ext not in supported: # Could be the file is zipped; try splitting again - path, ext = os.path.splitext(path) - ext = ext.strip(".") - - if ext not in supported: - raise ValueError(f"Unrecognized file type for: {init}") - - if ext == "json": # Assume json input is an association - return "asn" - - return ext - elif hasattr(init, "read") and hasattr(init, "seek"): - magic = init.read(5) - init.seek(0, 0) - - if not magic or len(magic) < 5: - raise ValueError(f"Cannot get file type of {str(init)}") - - if magic == b"#ASDF": - return "asdf" - - return "asn" - else: - raise ValueError(f"Cannot get file type of {str(init)}") From c4da77b0a069620d02738993d35caa7c33489033 Mon Sep 17 00:00:00 2001 From: Dave Davis Date: Wed, 26 Jul 2023 13:15:53 -0400 Subject: [PATCH 21/22] rcal-596 Update Changes --- CHANGES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index bd855ec6..697851ee 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,6 @@ 0.17.0 (unreleased) =================== -- Add check for filetypes for association processing [#241] +- Add checks for for association processing [#241] - Remove the ``random_utils`` module and make ``maker_utils`` entirely deterministic. [#217] From cb927e9f9f2e5e416e91e0eafcb3ece968fb3661 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 26 Jul 2023 17:19:57 +0000 Subject: [PATCH 22/22] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/roman_datamodels/datamodels/_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/roman_datamodels/datamodels/_utils.py b/src/roman_datamodels/datamodels/_utils.py index a9bb6f2e..12d5b15a 100644 --- a/src/roman_datamodels/datamodels/_utils.py +++ b/src/roman_datamodels/datamodels/_utils.py @@ -3,10 +3,10 @@ the open/factory function for creating datamodels """ import warnings +from pathlib import Path import asdf from astropy.utils import minversion -from pathlib import Path from roman_datamodels import validate