Skip to content

Commit 6cd09dd

Browse files
authored
Merge pull request #3319 from snbianco/missions-filter-lists
Accept lists for MastMissions query criteria
2 parents 427b596 + 97643ee commit 6cd09dd

File tree

6 files changed

+76
-30
lines changed

6 files changed

+76
-30
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ mast
9494
- Added ``verbose`` parameter to ``Observations.get_cloud_uris`` to control whether warnings are logged when a product cannot
9595
be found in the cloud. [#3314]
9696

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

98100
Infrastructure, Utility and Other Changes and Additions
99101
-------------------------------------------------------

astroquery/mast/missions.py

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,34 @@ def _validate_criteria(self, **criteria):
149149
)
150150
raise InvalidQueryError(error_msg)
151151

152+
def _build_params_from_criteria(self, params, **criteria):
153+
"""
154+
Build the parameters for the API request based on the provided criteria.
155+
156+
Parameters
157+
----------
158+
params : dict
159+
Dictionary to store the parameters for the API request.
160+
**criteria
161+
Keyword arguments representing criteria filters to apply.
162+
"""
163+
# Add each criterion to the params dictionary
164+
params['conditions'] = []
165+
for prop, value in criteria.items():
166+
if prop not in self._search_option_fields:
167+
if isinstance(value, list):
168+
# Convert to comma-separated string if passed as a list
169+
value = ','.join(str(item) for item in value)
170+
params['conditions'].append({prop: value})
171+
else:
172+
if prop == 'sort_by' and isinstance(value, str):
173+
# Convert to list if passed as a string
174+
value = [value]
175+
if prop == 'sort_desc' and isinstance(value, bool):
176+
# Convert to list if passed as a boolean
177+
value = [value]
178+
params[prop] = value
179+
152180
@class_or_instance
153181
def query_region_async(self, coordinates, *, radius=3*u.arcmin, limit=5000, offset=0,
154182
select_cols=None, **criteria):
@@ -172,12 +200,15 @@ def query_region_async(self, coordinates, *, radius=3*u.arcmin, limit=5000, offs
172200
Optional and default is 0
173201
the number of records you wish to skip before selecting records.
174202
select_cols: list, None
175-
Default None. Names of columns that will be included in the astropy table
203+
Default None. Names of columns that will be included in the result table.
204+
If None, a default set of columns will be returned.
176205
**criteria
177206
Other mission-specific criteria arguments.
178207
All valid filters can be found using `~astroquery.mast.missions.MastMissionsClass.get_column_list`
179208
function.
180209
For example, one can specify the output columns(select_cols) or use other filters(conditions).
210+
To filter by multiple values for a single column, pass in a list of values or
211+
a comma-separated string of values.
181212
182213
Returns
183214
-------
@@ -210,13 +241,7 @@ def query_region_async(self, coordinates, *, radius=3*u.arcmin, limit=5000, offs
210241
'offset': offset,
211242
'select_cols': select_cols}
212243

213-
params['conditions'] = []
214-
# adding additional user specified parameters
215-
for prop, value in criteria.items():
216-
if prop not in self._search_option_fields:
217-
params['conditions'].append({prop: value})
218-
else:
219-
params[prop] = value
244+
self._build_params_from_criteria(params, **criteria)
220245

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

@@ -245,7 +270,8 @@ def query_criteria_async(self, *, coordinates=None, objectname=None, radius=3*u.
245270
Optional and default is 0.
246271
the number of records you wish to skip before selecting records.
247272
select_cols: list, None
248-
Default None. Names of columns that will be included in the astropy table
273+
Default None. Names of columns that will be included in the result table.
274+
If None, a default set of columns will be returned.
249275
resolver : str, optional
250276
The resolver to use when resolving a named target into coordinates. Valid options are "SIMBAD" and "NED".
251277
If not specified, the default resolver order will be used. Please see the
@@ -259,6 +285,8 @@ def query_criteria_async(self, *, coordinates=None, objectname=None, radius=3*u.
259285
and all fields listed in the column documentation for the mission being queried.
260286
List of all valid fields that can be used to match results on criteria can be retrieved by calling
261287
`~astroquery.mast.missions.MastMissionsClass.get_column_list` function.
288+
To filter by multiple values for a single column, pass in a list of values or
289+
a comma-separated string of values.
262290
263291
Returns
264292
-------
@@ -296,12 +324,7 @@ def query_criteria_async(self, *, coordinates=None, objectname=None, radius=3*u.
296324
if not self._service_api_connection.check_catalogs_criteria_params(criteria):
297325
raise InvalidQueryError("At least one non-positional criterion must be supplied.")
298326

299-
params['conditions'] = []
300-
for prop, value in criteria.items():
301-
if prop not in self._search_option_fields:
302-
params['conditions'].append({prop: value})
303-
else:
304-
params[prop] = value
327+
self._build_params_from_criteria(params, **criteria)
305328

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

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

astroquery/mast/services.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
from ..query import BaseQuery
1919
from ..utils import async_to_sync
2020
from ..utils.class_or_instance import class_or_instance
21-
from ..exceptions import TimeoutError, NoResultsWarning
21+
from ..exceptions import InvalidQueryError, TimeoutError, NoResultsWarning
2222

2323
from . import conf
2424

@@ -203,6 +203,12 @@ def _request(self, method, url, params=None, data=None, headers=None,
203203
if (time.time() - start_time) >= self.TIMEOUT:
204204
raise TimeoutError("Timeout limit of {} exceeded.".format(self.TIMEOUT))
205205

206+
if response.status_code in [400, 500]:
207+
raise InvalidQueryError('The server was unable to process the request due to invalid input. '
208+
'Please check the query parameters and try again. Use the '
209+
'`MastMissions.get_column_list` method to see the available searchable '
210+
'columns and their expected data types.')
211+
206212
response.raise_for_status()
207213
return response
208214

astroquery/mast/tests/test_mast.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -227,8 +227,10 @@ def test_missions_query_object(patch_post):
227227

228228
def test_missions_query_region(patch_post):
229229
result = mast.MastMissions.query_region(regionCoords,
230+
sci_instrume=['ACS', 'WFPC'],
230231
radius=0.002 * u.deg,
231-
select_cols=['sci_pep_id'])
232+
select_cols=['sci_pep_id'],
233+
sort_by=['sci_pep_id'])
232234
assert isinstance(result, Table)
233235
assert len(result) > 0
234236

@@ -263,11 +265,13 @@ def test_missions_query_criteria(patch_post):
263265
result = mast.MastMissions.query_criteria(
264266
coordinates=regionCoords,
265267
radius=3,
266-
sci_pep_id=12556,
268+
sci_pep_id=[12556, 8794],
267269
sci_obs_type='SPECTRUM',
268270
sci_instrume='stis,acs,wfc3,cos,fos,foc,nicmos,ghrs',
269271
sci_aec='S',
270-
select_cols=['sci_pep_id', 'sci_instrume']
272+
select_cols=['sci_pep_id', 'sci_instrume'],
273+
sort_by='sci_pep_id',
274+
sort_desc=True
271275
)
272276
assert isinstance(result, Table)
273277
assert len(result) > 0

astroquery/mast/tests/test_mast_remote.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,15 +98,17 @@ def test_missions_query_region(self):
9898
select_cols = ['sci_targname', 'sci_instrume']
9999
result = MastMissions.query_region("245.89675 -26.52575",
100100
radius=0.1,
101-
sci_instrume="WFC3, ACS",
102-
select_cols=select_cols
103-
)
101+
sci_instrume=["WFC3", "ACS"],
102+
select_cols=select_cols,
103+
sort_by="sci_data_set_name",
104+
sort_desc=True)
104105
assert isinstance(result, Table)
105106
assert len(result) > 0
106107
assert (result['ang_sep'].data.data.astype('float') < 0.1).all()
107108
ins_strip = np.char.strip(result['sci_instrume'].data)
108109
assert ((ins_strip == 'WFC3') | (ins_strip == 'ACS')).all()
109110
assert all(c in list(result.columns.keys()) for c in select_cols)
111+
assert list(result['sci_data_set_name']) == sorted(result['sci_data_set_name'], reverse=True)
110112

111113
def test_missions_query_object_async(self):
112114
response = MastMissions.query_object_async("M4", radius=0.1)
@@ -163,6 +165,12 @@ def test_missions_query_criteria(self):
163165
MastMissions.query_criteria(coordinates="245.89675 -26.52575",
164166
radius=1)
165167

168+
# Raise error if invalid input is given
169+
with pytest.raises(InvalidQueryError):
170+
MastMissions.query_criteria(coordinates="245.89675 -26.52575",
171+
radius=1,
172+
sci_pep_id="invalid")
173+
166174
def test_missions_query_criteria_invalid_keyword(self):
167175
# Attempt to make a criteria query with invalid keyword
168176
with pytest.raises(InvalidQueryError) as err_no_alt:

docs/mast/mast_missions.rst

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -63,9 +63,9 @@ Keyword arguments can also be used to refine results further. The following para
6363

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

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

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

7171
- ``select_cols``: A list of columns to be returned in the response.
@@ -88,7 +88,7 @@ certain radius value of that point. This type of search is also known as a cone
8888
... radius=3,
8989
... sci_pep_id=12556,
9090
... select_cols=["sci_stop_time", "sci_targname", "sci_start_time", "sci_status"],
91-
... sort_by=['sci_targname'])
91+
... sort_by='sci_targname')
9292
>>> results[:5] # doctest: +IGNORE_OUTPUT
9393
<Table masked=True length=5>
9494
search_pos sci_data_set_name sci_targname sci_start_time sci_stop_time ang_sep sci_status
@@ -123,7 +123,7 @@ function.
123123
>>> results = missions.query_object('M101',
124124
... radius=3,
125125
... select_cols=["sci_stop_time", "sci_targname", "sci_start_time", "sci_status"],
126-
... sort_by=['sci_targname'])
126+
... sort_by='sci_targname')
127127
>>> results[:5] # doctest: +IGNORE_OUTPUT
128128
<Table masked=True length=5>
129129
search_pos sci_data_set_name sci_targname sci_start_time sci_stop_time ang_sep sci_status
@@ -145,7 +145,7 @@ function.
145145

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

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

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

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

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

179179
>>> results = missions.query_criteria(sci_obs_type="IMAGE",
180180
... sci_instrume="!COS",
181-
... sci_spec_1234="F150W, F105W, F110W",
181+
... sci_spec_1234=["F150W", "F105W", "F110W"],
182182
... sci_dec=">0",
183183
... sci_actual_duration="1000..2000",
184184
... sci_targname="*GAL*",

0 commit comments

Comments
 (0)