Skip to content

Commit d3ecf0d

Browse files
committed
Parameter checking on Missions queries
Mock MastMissions.get_column_list Increase code coverage
1 parent 8ae63fb commit d3ecf0d

File tree

6 files changed

+677
-69
lines changed

6 files changed

+677
-69
lines changed

CHANGES.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,8 @@ mast
223223
- Deprecated ``enable_cloud_dataset`` and ``disable_cloud_dataset`` in classes where they
224224
are non-operational. They will be removed in a future release. [#3113]
225225

226+
- Present users with an error when nonexistent query criteria are used in ``mast.MastMissions`` query functions. [#3126]
227+
226228
mpc
227229
^^^
228230

astroquery/mast/missions.py

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
This module contains methods for searching MAST missions.
77
"""
88

9-
import requests
9+
import difflib
1010
import warnings
1111

1212
from astropy.table import Table
@@ -40,6 +40,7 @@ def __init__(self, *, mission='hst', service='search'):
4040
self.service = service
4141
self.mission = mission
4242
self.limit = 5000
43+
self.columns = dict() # Info about columns for each mission
4344

4445
service_dict = {self.service: {'path': self.service, 'args': {}}}
4546
self._service_api_connection.set_service_params(service_dict, f"{self.service}/{self.mission}")
@@ -69,6 +70,37 @@ def _parse_result(self, response, *, verbose=False): # Used by the async_to_syn
6970

7071
return results
7172

73+
def _validate_criteria(self, **criteria):
74+
"""
75+
Check that criteria keyword arguments are valid column names for the mission.
76+
Raises InvalidQueryError if a criteria argument is invalid.
77+
78+
Parameters
79+
----------
80+
**kwargs
81+
Keyword arguments representing criteria filters to apply.
82+
83+
Raises
84+
-------
85+
InvalidQueryError
86+
If a keyword does not match any valid column names, an error is raised that suggests the closest
87+
matching column name, if available.
88+
"""
89+
# Ensure that self.columns in populated
90+
self.get_column_list()
91+
92+
# Check each criteria argument for validity
93+
valid_cols = self.columns[self.mission]['name']
94+
for kwd in criteria.keys():
95+
if kwd not in valid_cols and kwd not in self._search_option_fields:
96+
closest_match = difflib.get_close_matches(kwd, valid_cols, n=1)
97+
error_msg = (
98+
f"Filter '{kwd}' does not exist. Did you mean '{closest_match[0]}'?"
99+
if closest_match
100+
else f"Filter '{kwd}' does not exist."
101+
)
102+
raise InvalidQueryError(error_msg)
103+
72104
@class_or_instance
73105
def query_region_async(self, coordinates, *, radius=3*u.arcmin, limit=5000, offset=0, **kwargs):
74106
"""
@@ -104,6 +136,9 @@ def query_region_async(self, coordinates, *, radius=3*u.arcmin, limit=5000, offs
104136

105137
self.limit = limit
106138

139+
# Check that criteria arguments are valid
140+
self._validate_criteria(**kwargs)
141+
107142
# Put coordinates and radius into consistent format
108143
coordinates = commons.parse_coordinates(coordinates)
109144

@@ -170,6 +205,9 @@ def query_criteria_async(self, *, coordinates=None, objectname=None, radius=3*u.
170205

171206
self.limit = limit
172207

208+
# Check that criteria arguments are valid
209+
self._validate_criteria(**criteria)
210+
173211
if objectname or coordinates:
174212
coordinates = utils.parse_input_location(coordinates, objectname)
175213

@@ -237,25 +275,29 @@ def get_column_list(self):
237275
238276
Returns
239277
-------
240-
response : `~astropy.table.Table` that contains columns names, types and their descriptions
278+
response : `~astropy.table.Table` that contains columns names, types, and descriptions
241279
"""
242280

243-
url = f"{conf.server}/search/util/api/v0.1/column_list?mission={self.mission}"
244-
245-
try:
246-
results = requests.get(url)
247-
results = results.json()
248-
rows = []
249-
for result in results:
250-
result.pop('field_name')
251-
result.pop('queryable')
252-
result.pop('indexed')
253-
result.pop('default_output')
254-
rows.append((result['column_name'], result['qual_type'], result['description']))
255-
data_table = Table(rows=rows, names=('name', 'data_type', 'description'))
256-
return data_table
257-
except Exception:
258-
raise Exception(f"Error occurred while trying to get column list for mission {self.mission}")
281+
if not self.columns.get(self.mission):
282+
try:
283+
# Send server request to get column list for current mission
284+
params = {'mission': self.mission}
285+
resp = utils._simple_request(f'{conf.server}/search/util/api/v0.1/column_list', params)
286+
287+
# Parse JSON and extract necessary info
288+
results = resp.json()
289+
rows = [
290+
(result['column_name'], result['qual_type'], result['description'])
291+
for result in results
292+
]
293+
294+
# Create Table with parsed data
295+
col_table = Table(rows=rows, names=('name', 'data_type', 'description'))
296+
self.columns[self.mission] = col_table
297+
except Exception:
298+
raise Exception(f'Error occurred while trying to get column list for mission {self.mission}')
299+
300+
return self.columns[self.mission]
259301

260302

261303
MastMissions = MastMissionsClass()

0 commit comments

Comments
 (0)