Skip to content

Accept lists for MastMissions query criteria #3319

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

Merged
merged 2 commits into from
Jun 5, 2025
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
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ mast
- Added ``verbose`` parameter to ``Observations.get_cloud_uris`` to control whether warnings are logged when a product cannot
be found in the cloud. [#3314]

- Improved ``MastMissions`` queries to accept lists for query critieria values, in addition to comma-delimited strings. [#3319]


Infrastructure, Utility and Other Changes and Additions
-------------------------------------------------------
Expand Down
58 changes: 42 additions & 16 deletions astroquery/mast/missions.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,34 @@ def _validate_criteria(self, **criteria):
)
raise InvalidQueryError(error_msg)

def _build_params_from_criteria(self, params, **criteria):
"""
Build the parameters for the API request based on the provided criteria.

Parameters
----------
params : dict
Dictionary to store the parameters for the API request.
**criteria
Keyword arguments representing criteria filters to apply.
"""
# Add each criterion to the params dictionary
params['conditions'] = []
for prop, value in criteria.items():
if prop not in self._search_option_fields:
if isinstance(value, list):
# Convert to comma-separated string if passed as a list
value = ','.join(str(item) for item in value)
params['conditions'].append({prop: value})
else:
if prop == 'sort_by' and isinstance(value, str):
# Convert to list if passed as a string
value = [value]
if prop == 'sort_desc' and isinstance(value, bool):
# Convert to list if passed as a boolean
value = [value]
params[prop] = value

@class_or_instance
def query_region_async(self, coordinates, *, radius=3*u.arcmin, limit=5000, offset=0,
select_cols=None, **criteria):
Expand All @@ -172,12 +200,15 @@ def query_region_async(self, coordinates, *, radius=3*u.arcmin, limit=5000, offs
Optional and default is 0
the number of records you wish to skip before selecting records.
select_cols: list, None
Default None. Names of columns that will be included in the astropy table
Default None. Names of columns that will be included in the result table.
If None, a default set of columns will be returned.
**criteria
Other mission-specific criteria arguments.
All valid filters can be found using `~astroquery.mast.missions.MastMissionsClass.get_column_list`
function.
For example, one can specify the output columns(select_cols) or use other filters(conditions).
To filter by multiple values for a single column, pass in a list of values or
a comma-separated string of values.

Returns
-------
Expand Down Expand Up @@ -210,13 +241,7 @@ def query_region_async(self, coordinates, *, radius=3*u.arcmin, limit=5000, offs
'offset': offset,
'select_cols': select_cols}

params['conditions'] = []
# adding additional user specified parameters
for prop, value in criteria.items():
if prop not in self._search_option_fields:
params['conditions'].append({prop: value})
else:
params[prop] = value
self._build_params_from_criteria(params, **criteria)

return self._service_api_connection.missions_request_async(self.service, params)

Expand Down Expand Up @@ -245,7 +270,8 @@ def query_criteria_async(self, *, coordinates=None, objectname=None, radius=3*u.
Optional and default is 0.
the number of records you wish to skip before selecting records.
select_cols: list, None
Default None. Names of columns that will be included in the astropy table
Default None. Names of columns that will be included in the result table.
If None, a default set of columns will be returned.
resolver : str, optional
The resolver to use when resolving a named target into coordinates. Valid options are "SIMBAD" and "NED".
If not specified, the default resolver order will be used. Please see the
Expand All @@ -259,6 +285,8 @@ def query_criteria_async(self, *, coordinates=None, objectname=None, radius=3*u.
and all fields listed in the column documentation for the mission being queried.
List of all valid fields that can be used to match results on criteria can be retrieved by calling
`~astroquery.mast.missions.MastMissionsClass.get_column_list` function.
To filter by multiple values for a single column, pass in a list of values or
a comma-separated string of values.

Returns
-------
Expand Down Expand Up @@ -296,12 +324,7 @@ def query_criteria_async(self, *, coordinates=None, objectname=None, radius=3*u.
if not self._service_api_connection.check_catalogs_criteria_params(criteria):
raise InvalidQueryError("At least one non-positional criterion must be supplied.")

params['conditions'] = []
for prop, value in criteria.items():
if prop not in self._search_option_fields:
params['conditions'].append({prop: value})
else:
params[prop] = value
self._build_params_from_criteria(params, **criteria)

return self._service_api_connection.missions_request_async(self.service, params)

Expand All @@ -327,7 +350,8 @@ def query_object_async(self, objectname, *, radius=3*u.arcmin, limit=5000, offse
Optional and default is 0.
the number of records you wish to skip before selecting records.
select_cols: list, None
Default None. Names of columns that will be included in the astropy table
Default None. Names of columns that will be included in the result table.
If None, a default set of columns will be returned.
resolver : str, optional
The resolver to use when resolving a named target into coordinates. Valid options are "SIMBAD" and "NED".
If not specified, the default resolver order will be used. Please see the
Expand All @@ -338,6 +362,8 @@ def query_object_async(self, objectname, *, radius=3*u.arcmin, limit=5000, offse
All valid filters can be found using `~astroquery.mast.missions.MastMissionsClass.get_column_list`
function.
For example, one can specify the output columns(select_cols) or use other filters(conditions).
To filter by multiple values for a single column, pass in a list of values or
a comma-separated string of values.

Returns
-------
Expand Down
8 changes: 7 additions & 1 deletion astroquery/mast/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from ..query import BaseQuery
from ..utils import async_to_sync
from ..utils.class_or_instance import class_or_instance
from ..exceptions import TimeoutError, NoResultsWarning
from ..exceptions import InvalidQueryError, TimeoutError, NoResultsWarning

from . import conf

Expand Down Expand Up @@ -203,6 +203,12 @@
if (time.time() - start_time) >= self.TIMEOUT:
raise TimeoutError("Timeout limit of {} exceeded.".format(self.TIMEOUT))

if response.status_code in [400, 500]:
raise InvalidQueryError('The server was unable to process the request due to invalid input. '

Check warning on line 207 in astroquery/mast/services.py

View check run for this annotation

Codecov / codecov/patch

astroquery/mast/services.py#L206-L207

Added lines #L206 - L207 were not covered by tests
'Please check the query parameters and try again. Use the '
'`MastMissions.get_column_list` method to see the available searchable '
'columns and their expected data types.')

response.raise_for_status()
return response

Expand Down
10 changes: 7 additions & 3 deletions astroquery/mast/tests/test_mast.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,10 @@ def test_missions_query_object(patch_post):

def test_missions_query_region(patch_post):
result = mast.MastMissions.query_region(regionCoords,
sci_instrume=['ACS', 'WFPC'],
radius=0.002 * u.deg,
select_cols=['sci_pep_id'])
select_cols=['sci_pep_id'],
sort_by=['sci_pep_id'])
assert isinstance(result, Table)
assert len(result) > 0

Expand Down Expand Up @@ -263,11 +265,13 @@ def test_missions_query_criteria(patch_post):
result = mast.MastMissions.query_criteria(
coordinates=regionCoords,
radius=3,
sci_pep_id=12556,
sci_pep_id=[12556, 8794],
sci_obs_type='SPECTRUM',
sci_instrume='stis,acs,wfc3,cos,fos,foc,nicmos,ghrs',
sci_aec='S',
select_cols=['sci_pep_id', 'sci_instrume']
select_cols=['sci_pep_id', 'sci_instrume'],
sort_by='sci_pep_id',
sort_desc=True
)
assert isinstance(result, Table)
assert len(result) > 0
Expand Down
14 changes: 11 additions & 3 deletions astroquery/mast/tests/test_mast_remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,17 @@ def test_missions_query_region(self):
select_cols = ['sci_targname', 'sci_instrume']
result = MastMissions.query_region("245.89675 -26.52575",
radius=0.1,
sci_instrume="WFC3, ACS",
select_cols=select_cols
)
sci_instrume=["WFC3", "ACS"],
select_cols=select_cols,
sort_by="sci_data_set_name",
sort_desc=True)
assert isinstance(result, Table)
assert len(result) > 0
assert (result['ang_sep'].data.data.astype('float') < 0.1).all()
ins_strip = np.char.strip(result['sci_instrume'].data)
assert ((ins_strip == 'WFC3') | (ins_strip == 'ACS')).all()
assert all(c in list(result.columns.keys()) for c in select_cols)
assert list(result['sci_data_set_name']) == sorted(result['sci_data_set_name'], reverse=True)

def test_missions_query_object_async(self):
response = MastMissions.query_object_async("M4", radius=0.1)
Expand Down Expand Up @@ -163,6 +165,12 @@ def test_missions_query_criteria(self):
MastMissions.query_criteria(coordinates="245.89675 -26.52575",
radius=1)

# Raise error if invalid input is given
with pytest.raises(InvalidQueryError):
MastMissions.query_criteria(coordinates="245.89675 -26.52575",
radius=1,
sci_pep_id="invalid")

def test_missions_query_criteria_invalid_keyword(self):
# Attempt to make a criteria query with invalid keyword
with pytest.raises(InvalidQueryError) as err_no_alt:
Expand Down
14 changes: 7 additions & 7 deletions docs/mast/mast_missions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ Keyword arguments can also be used to refine results further. The following para

- ``offset``: Skip the first ***n*** results. Useful for paging through results.

- ``sort_by``: A list of field names to sort by.
- ``sort_by``: A string or list of field names to sort by.

- ``sort_desc``: A list of booleans (one for each field specified in ``sort_by``),
- ``sort_desc``: A boolean or list of booleans (one for each field specified in ``sort_by``),
describing if each field should be sorted in descending order (``True``) or ascending order (``False``).

- ``select_cols``: A list of columns to be returned in the response.
Expand All @@ -88,7 +88,7 @@ certain radius value of that point. This type of search is also known as a cone
... radius=3,
... sci_pep_id=12556,
... select_cols=["sci_stop_time", "sci_targname", "sci_start_time", "sci_status"],
... sort_by=['sci_targname'])
... sort_by='sci_targname')
>>> results[:5] # doctest: +IGNORE_OUTPUT
<Table masked=True length=5>
search_pos sci_data_set_name sci_targname sci_start_time sci_stop_time ang_sep sci_status
Expand Down Expand Up @@ -123,7 +123,7 @@ function.
>>> results = missions.query_object('M101',
... radius=3,
... select_cols=["sci_stop_time", "sci_targname", "sci_start_time", "sci_status"],
... sort_by=['sci_targname'])
... sort_by='sci_targname')
>>> results[:5] # doctest: +IGNORE_OUTPUT
<Table masked=True length=5>
search_pos sci_data_set_name sci_targname sci_start_time sci_stop_time ang_sep sci_status
Expand All @@ -145,7 +145,7 @@ function.

>>> results = missions.query_criteria(sci_start_time=">=2021-01-01 00:00:00",
... select_cols=["sci_stop_time", "sci_targname", "sci_start_time", "sci_status", "sci_pep_id"],
... sort_by=['sci_pep_id'],
... sort_by='sci_pep_id',
... limit=1000,
... offset=1000) # doctest: +IGNORE_WARNINGS
... # MaxResultsWarning('Maximum results returned, may not include all sources within radius.')
Expand All @@ -156,7 +156,7 @@ Here are some tips and tricks for writing more advanced queries:

- To exclude and filter out a certain value from the results, prepend the value with ``!``.

- To filter by multiple values for a single column, use a string of values delimited by commas.
- To filter by multiple values for a single column, use a list of values or a string of values delimited by commas.

- For columns with numeric or date data types, filter using comparison values (``<``, ``>``, ``<=``, ``>=``).

Expand All @@ -178,7 +178,7 @@ Here are some tips and tricks for writing more advanced queries:

>>> results = missions.query_criteria(sci_obs_type="IMAGE",
... sci_instrume="!COS",
... sci_spec_1234="F150W, F105W, F110W",
... sci_spec_1234=["F150W", "F105W", "F110W"],
... sci_dec=">0",
... sci_actual_duration="1000..2000",
... sci_targname="*GAL*",
Expand Down
Loading