Skip to content

MAST: Adding read_product function to mast.observations#3610

Open
AlexReedy wants to merge 15 commits into
astropy:mainfrom
AlexReedy:read_product_function
Open

MAST: Adding read_product function to mast.observations#3610
AlexReedy wants to merge 15 commits into
astropy:mainfrom
AlexReedy:read_product_function

Conversation

@AlexReedy

@AlexReedy AlexReedy commented Jun 12, 2026

Copy link
Copy Markdown

A function of the Mast.Observations class to read product from S3 to memory.

Observations.read_product() takes both FITS and ASDF file URIs and stores them using astropy.io.fits.open and asdf.open respectively.

Connection to S3 is done using fsspec

Changes made

  1. Mast/observations - added read_product() function and import checks for asdf and fsspec
  2. Mast/test - testing suite for read_product that mocks S3 connection and data and checks functionality (test_mast.py) as well as tests live connection to S3 and confirms product type return.

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.37838% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.18%. Comparing base (70c49c0) to head (f9a9f45).
⚠️ Report is 3 commits behind head on main.

Files with missing lines Patch % Lines
astroquery/mast/observations.py 78.37% 8 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main    #3610   +/-   ##
=======================================
  Coverage   73.17%   73.18%           
=======================================
  Files         227      227           
  Lines       21078    21115   +37     
=======================================
+ Hits        15423    15452   +29     
- Misses       5655     5663    +8     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@AlexReedy AlexReedy changed the title Adding read_product function to mast.observations MAST: Adding read_product function to mast.observations Jun 12, 2026
@AlexReedy
AlexReedy marked this pull request as ready for review June 23, 2026 15:10
@snbianco
snbianco self-requested a review July 1, 2026 17:55
@snbianco snbianco added the mast label Jul 1, 2026

@snbianco snbianco left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I had a few comments, but this looks good!



@pytest.fixture
def mock_asdf_open(mocker):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you add:

pytest.importorskip("asdf")
pytest.importorskip("fsspec")

tests that use this fixture will automatically skip if asdf or fsspec are not installed. I think this would be preferable to mocking mock_fsspec_open.

Comment thread astroquery/mast/tests/test_mast.py Outdated
Observations.read_product("file.fits")


def test_observations_read_product_asdf_missing(monkeypatch):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could you combine this test with the one before it using parametrization?

Comment thread astroquery/mast/observations.py Outdated
# Forces the path to be lowercase for the extension checks. This is only used for the checks
path = str(product_path).lower()

# Checks users enviornment for fsspec, required for both fits and asdf

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Suggested change
# Checks users enviornment for fsspec, required for both fits and asdf
# Checks users environment for fsspec, required for both fits and asdf

data_product = fits.open(product_path, fsspec_kwargs={"anon": True}, **kwargs)
log.info(f"Loaded: {product_path}")
return data_product
except Exception as e:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you be more specific about the exception here? Or if you want to catch generally, add a comment explaining why.

Comment thread astroquery/mast/observations.py Outdated

# Logic for reading ASDF files
elif path.endswith(".asdf"):
if fsspec is None:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Since this package is needed for both FITS and ASDF, can you check that it exists in the environment outside of this block so that it's not there twice?

raise ImportError('The "asdf" package is required to read ASDF files. Please install it with '
'`pip install asdf` or install astroquery with optional dependencies using '
'`pip install astroquery[all]`.')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd also like to raise warnings if the optional packages are not found. Something like this:

for package in ["gwcs", "lz4", "roman_datamodels"]:
                    if importlib.util.find_spec(package) is None:
                        warnings.warn(f'The "{package}" package is encouraged when reading ASDF files from the '
                                      'Roman Space Telescope mission. Please install it with `pip install {package}` '
                                      'or install astroquery with optional dependencies using '
                                      '`pip install astroquery[all]`.', ImportWarning)

data_product = asdf.open(f, ignore_unrecognized_tag=ignore_unrecognized)
log.info(f"Loaded: {product_path}")
return data_product
except Exception as e:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use a more specific exception here if you can

.. doctest-remote-data::

>>> from astroquery.mast import Observations
>>> fits_product = Observations.read_product(product_path="s3://stpubdata/hst/public/u9o4/u9o40504m/u9o40504m_c3m.fits")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you add a fits_product.info() and asdf_product.info() just to demonstrate that actual objects with data are being returned?

@bsipocz bsipocz left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

please rebase, and once I have a passing review from Sam I'll come back and do a final review.

Comment thread tox.ini
oldestdeps-alldeps: regions==0.5
oldestdeps-alldeps: astropy-healpix==0.7
oldestdeps-alldeps: roman_datamodels==0.11.0
oldestdeps-alldeps: gwcs==0.18

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

gwcs is not listed as a dependency, so including it here makes little sense to me?

But also, if we add all the 3 packages as optional dependencies we will need to make sure they don't introduce some indirect limitations on supported versions.

Comment on lines +1057 to +1066
@pytest.mark.parametrize(
"product_path, expected_type",
[
(
"s3://stpubdata/hst/public/u24r/u24r0102t/u24r0102t_c1f.fits",
fits.HDUList,
)
],
)
def test_observations_read_product(self, product_path, expected_type):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we allow long lines in astroquery, please reformat it back to something less verbose and whitespace-y

import asdf
except ImportError:
asdf = None

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You already do skipifs, so is this really necessary? or maybe you can use @pytest.mark.skipif instead of the importorskip, so it's more consistent

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants