Skip to content

Commit af6b41b

Browse files
authored
Merge pull request #2321 from syed-gilani/missions_mast
Missions mast
2 parents b787ee2 + 47bd905 commit af6b41b

File tree

5 files changed

+83
-31
lines changed

5 files changed

+83
-31
lines changed

astroquery/mast/missions.py

Lines changed: 72 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
"""
88

99
import requests
10+
import warnings
1011

12+
from astropy.table import Table
1113
import astropy.units as u
1214
import astropy.coordinates as coord
1315

1416
from astroquery.utils import commons, async_to_sync
1517
from astroquery.utils.class_or_instance import class_or_instance
16-
from astroquery.exceptions import InvalidQueryError
18+
from astroquery.exceptions import InvalidQueryError, MaxResultsWarning
1719

1820
from astroquery.mast import utils
1921
from astroquery.mast.core import MastQueryWithLogin
@@ -27,8 +29,7 @@
2729
class MastMissionsClass(MastQueryWithLogin):
2830
"""
2931
MastMissions search class.
30-
31-
Class that allows direct programatic access to the MAST search API for a given mission.
32+
Class that allows direct programatic access to retrieve metadata via the MAST search API for a given mission.
3233
"""
3334

3435
def __init__(self, *, mission='hst', service='search'):
@@ -38,11 +39,12 @@ def __init__(self, *, mission='hst', service='search'):
3839
'skip_count', 'user_fields']
3940
self.service = service
4041
self.mission = mission
42+
self.limit = 5000
4143

4244
service_dict = {self.service: {'path': self.service, 'args': {}}}
4345
self._service_api_connection.set_service_params(service_dict, f"{self.service}/{self.mission}")
4446

45-
def _parse_result(self, response, verbose=False): # Used by the async_to_sync decorator functionality
47+
def _parse_result(self, response, *, verbose=False): # Used by the async_to_sync decorator functionality
4648
"""
4749
Parse the results of a `~requests.Response` objects and return an `~astropy.table.Table` of results.
4850
@@ -53,17 +55,22 @@ def _parse_result(self, response, verbose=False): # Used by the async_to_sync d
5355
verbose : bool
5456
(presently does nothing - there is no output with verbose set to
5557
True or False)
56-
Default False. Setting to True provides more extensive output.
58+
Default False. Setting to True provides more extensive output.
5759
5860
Returns
5961
-------
6062
response : `~astropy.table.Table`
6163
"""
6264

63-
return self._service_api_connection._parse_result(response, verbose, data_key='results')
65+
results = self._service_api_connection._parse_result(response, verbose, data_key='results')
66+
if len(results) >= self.limit:
67+
warnings.warn("Maximum results returned, may not include all sources within radius.",
68+
MaxResultsWarning)
69+
70+
return results
6471

6572
@class_or_instance
66-
def query_region_async(self, coordinates, radius=3*u.arcmin, **kwargs):
73+
def query_region_async(self, coordinates, *, radius=3*u.arcmin, limit=5000, offset=0, **kwargs):
6774
"""
6875
Given a sky position and radius, returns a list of matching dataset IDs.
6976
@@ -77,17 +84,26 @@ def query_region_async(self, coordinates, radius=3*u.arcmin, **kwargs):
7784
The string must be parsable by `~astropy.coordinates.Angle`. The
7885
appropriate `~astropy.units.Quantity` object from
7986
`~astropy.units` may also be used. Defaults to 3 arcminutes.
87+
limit : int
88+
Optional and default is 5000.
89+
the maximun number of dataset IDs in the results.
90+
offset : int
91+
Optional and default is 0
92+
the number of records you wish to skip before selecting records.
8093
**kwargs
8194
Other mission-specific keyword args.
82-
These can be found at the following link
83-
https://mast.stsci.edu/search/docs/#/Hubble%20Search/post_search_hst_api_v0_1_search_post
95+
Any invalid keys are ignored by the API.
96+
All valid key names can be found using `~astroquery.mast.missions.MastMissionsClass.get_column_list`
97+
function.
8498
For example one can specify the output columns(select_cols) or use other filters(conditions)
8599
86100
Returns
87101
-------
88102
response : list of `~requests.Response`
89103
"""
90104

105+
self.limit = limit
106+
91107
# Put coordinates and radius into consistant format
92108
coordinates = commons.parse_coordinates(coordinates)
93109

@@ -97,7 +113,9 @@ def query_region_async(self, coordinates, radius=3*u.arcmin, **kwargs):
97113
# basic params
98114
params = {'target': [f"{coordinates.ra.deg} {coordinates.dec.deg}"],
99115
'radius': radius.arcmin,
100-
'radius_units': 'arcminutes'}
116+
'radius_units': 'arcminutes',
117+
'limit': limit,
118+
'offset': offset}
101119

102120
params['conditions'] = []
103121
# adding additional user specified parameters
@@ -110,29 +128,47 @@ def query_region_async(self, coordinates, radius=3*u.arcmin, **kwargs):
110128
return self._service_api_connection.service_request_async(self.service, params, use_json=True)
111129

112130
@class_or_instance
113-
def query_criteria_async(self, **criteria):
131+
def query_criteria_async(self, *, coordinates=None, objectname=None, radius=3*u.arcmin,
132+
limit=5000, offset=0, select_cols=[], **criteria):
114133
"""
115134
Given a set of search criteria, returns a list of mission metadata.
116135
117136
Parameters
118137
----------
138+
coordinates : str or `~astropy.coordinates` object
139+
The target around which to search. It may be specified as a
140+
string or as the appropriate `~astropy.coordinates` object.
141+
objectname : str
142+
The name of the target around which to search.
143+
radius : str or `~astropy.units.Quantity` object, optional
144+
Default 3 degrees.
145+
The string must be parsable by `~astropy.coordinates.Angle`. The
146+
appropriate `~astropy.units.Quantity` object from
147+
`~astropy.units` may also be used. Defaults to 3 arcminutes.
148+
limit : int
149+
Optional and default is 5000.
150+
the maximun number of dataset IDs in the results.
151+
offset : int
152+
Optional and default is 0.
153+
the number of records you wish to skip before selecting records.
154+
select_cols: list
155+
names of columns that will be included in the astropy table
119156
**criteria
120157
Criteria to apply. At least one non-positional criteria must be supplied.
121-
Valid criteria are coordinates, objectname, radius (as in `query_region` and `query_object`),
158+
Valid criteria are coordinates, objectname, radius (as in
159+
`~astroquery.mast.missions.MastMissionsClass.query_region` and
160+
`~astroquery.mast.missions.MastMissionsClass.query_object` functions),
122161
and all fields listed in the column documentation for the mission being queried.
123-
Fields that can be used to match results on criteria. See the TAP schema link below for all field names.
124-
https://vao.stsci.edu/missionmast/tapservice.aspx/tables
125-
some common fields for criteria are sci_pep_id, sci_spec_1234 and sci_actual_duration.
162+
Any invalid keys passed in criteria are ignored by the API.
163+
List of all valid fields that can be used to match results on criteria can be retrieved by calling
164+
`~astroquery.mast.missions.MastMissionsClass.get_column_list` function.
126165
127166
Returns
128167
-------
129168
response : list of `~requests.Response`
130169
"""
131170

132-
# Seperating any position info from the rest of the filters
133-
coordinates = criteria.pop('coordinates', None)
134-
objectname = criteria.pop('objectname', None)
135-
radius = criteria.pop('radius', 0.2*u.deg)
171+
self.limit = limit
136172

137173
if objectname or coordinates:
138174
coordinates = utils.parse_input_location(coordinates, objectname)
@@ -141,7 +177,7 @@ def query_criteria_async(self, **criteria):
141177
radius = coord.Angle(radius, u.arcmin)
142178

143179
# build query
144-
params = {}
180+
params = {"limit": self.limit, "offset": offset, 'select_cols': select_cols}
145181
if coordinates:
146182
params["target"] = [f"{coordinates.ra.deg} {coordinates.dec.deg}"]
147183
params["radius"] = radius.arcmin
@@ -160,7 +196,7 @@ def query_criteria_async(self, **criteria):
160196
return self._service_api_connection.service_request_async(self.service, params, use_json=True)
161197

162198
@class_or_instance
163-
def query_object_async(self, objectname, radius=3*u.arcmin, **kwargs):
199+
def query_object_async(self, objectname, *, radius=3*u.arcmin, limit=5000, offset=0, **kwargs):
164200
"""
165201
Given an object name, returns a list of matching rows.
166202
@@ -173,10 +209,17 @@ def query_object_async(self, objectname, radius=3*u.arcmin, **kwargs):
173209
The string must be parsable by `~astropy.coordinates.Angle`.
174210
The appropriate `~astropy.units.Quantity` object from
175211
`~astropy.units` may also be used. Defaults to 3 arcminutes.
212+
limit : int
213+
Optional and default is 5000.
214+
the maximun number of dataset IDs in the results.
215+
offset : int
216+
Optional and default is 0.
217+
the number of records you wish to skip before selecting records.
176218
**kwargs
177219
Mission-specific keyword args.
178-
These can be found in the `service documentation <https://mast.stsci.edu/api/v0/_services.html>`__.
179-
for specific catalogs. For example one can specify the magtype for an HSC search.
220+
Any invalid keys are ignored by the API.
221+
All valid keys can be found by calling `~astroquery.mast.missions.MastMissionsClass.get_column_list`
222+
function.
180223
181224
Returns
182225
-------
@@ -185,7 +228,7 @@ def query_object_async(self, objectname, radius=3*u.arcmin, **kwargs):
185228

186229
coordinates = utils.resolve_object(objectname)
187230

188-
return self.query_region_async(coordinates, radius, **kwargs)
231+
return self.query_region_async(coordinates, radius=radius, limit=limit, offset=offset, **kwargs)
189232

190233
@class_or_instance
191234
def get_column_list(self):
@@ -194,20 +237,23 @@ def get_column_list(self):
194237
195238
Returns
196239
-------
197-
json data that contains columns names and their descriptions
240+
response : `~astropy.table.Table` that contains columns names, types and their descriptions
198241
"""
199242

200243
url = f"{conf.server}/search/util/api/v0.1/column_list?mission={self.mission}"
201244

202245
try:
203246
results = requests.get(url)
204247
results = results.json()
248+
rows = []
205249
for result in results:
206250
result.pop('field_name')
207251
result.pop('queryable')
208252
result.pop('indexed')
209253
result.pop('default_output')
210-
return results
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
211257
except Exception:
212258
raise Exception(f"Error occured while trying to get column list for mission {self.mission}")
213259

astroquery/mast/services.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ def _json_to_table(json_obj, data_key='data'):
3131
3232
Parameters
3333
----------
34-
json_obj : dict
34+
json_obj : data array or list of dictionaries
3535
A MAST microservice response JSON object (python dictionary)
3636
data_key : str
3737
string that contains the key name in json_obj that stores the data rows
@@ -50,6 +50,7 @@ def _json_to_table(json_obj, data_key='data'):
5050

5151
# for each item in info, store the type and column name
5252
# for each item in info, type has to be converted from DB data types (SQL server in most cases)
53+
# from missions_mast search service such as varchar, integer, float, boolean etc
5354
# to corresponding numpy type
5455
for idx, col, col_type, ignore_value in \
5556
[(idx, x['name'], x[type_key].lower(), None) for idx, x in enumerate(json_obj['info'])]:
@@ -78,6 +79,7 @@ def _json_to_table(json_obj, data_key='data'):
7879
# Step through data array of values
7980
col_data = np.array([x[idx] for x in json_obj[data_key]], dtype=object)
8081
except KeyError:
82+
# it's not a data array, fall back to using column name as it is array of dictionaries
8183
col_data = np.array([x[col] for x in json_obj[data_key]], dtype=object)
8284
if ignore_value is not None:
8385
col_data[np.where(np.equal(col_data, None))] = ignore_value

astroquery/mast/tests/data/mission_incorrect_results.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,5 +131,5 @@
131131
}
132132
]
133133
},
134-
"totalResults": 3,
134+
"totalResults": 3
135135
}

astroquery/mast/tests/test_mast.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Licensed under a 3-clause BSD style license - see LICENSE.rst
22

3+
import json
34
import os
45
import re
56
from shutil import copyfile
@@ -12,6 +13,7 @@
1213

1314
import astropy.units as u
1415

16+
from astroquery.mast.services import _json_to_table
1517
from astroquery.utils.mocks import MockResponse
1618
from astroquery.exceptions import InvalidQueryError, InputWarning
1719

@@ -258,6 +260,7 @@ def test_missions_query_criteria_async_with_missing_results(patch_post):
258260
obs_type,
259261
aec,
260262
aperture])
263+
table = _json_to_table(json.loads(responses), 'results')
261264

262265

263266
###################

docs/mast/mast.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ For a non positional search, select_cols would always include search_key and sci
229229
>>> from astropy.coordinates import SkyCoord
230230
>>> missions = MastMissions(mission='hst')
231231
>>> regionCoords = SkyCoord(210.80227, 54.34895, unit=('deg', 'deg'))
232-
>>> results = missions.query_region(regionCoords, 3, sci_pep_id=12556,
232+
>>> results = missions.query_region(regionCoords, radius=3, sci_pep_id=12556,
233233
... select_cols=["sci_stop_time", "sci_targname", "sci_start_time", "sci_status"],
234234
... sort_by=['sci_targname'])
235235
>>> results[:5] # doctest: +IGNORE_OUTPUT
@@ -254,7 +254,8 @@ of returned records. the default values for offset and limit is 0 and 5000 respe
254254
>>> missions = MastMissions()
255255
>>> results = missions.query_criteria(sci_start_time=">=2021-01-01 00:00:00",
256256
... select_cols=["sci_stop_time", "sci_targname", "sci_start_time", "sci_status", "sci_pep_id"],
257-
... sort_by=['sci_pep_id'], limit=1000, offset=1000)
257+
... sort_by=['sci_pep_id'], limit=1000, offset=1000) # doctest: +IGNORE_WARNINGS
258+
... # MaxResultsWarning('Maximum results returned, may not include all sources within radius.')
258259
>>> len(results)
259260
1000
260261

@@ -263,7 +264,7 @@ Metadata queries can also be performed using object names with the
263264

264265
.. doctest-remote-data::
265266

266-
>>> results = missions.query_object('M101', 3, select_cols=["sci_stop_time", "sci_targname", "sci_start_time", "sci_status"],
267+
>>> results = missions.query_object('M101', radius=3, select_cols=["sci_stop_time", "sci_targname", "sci_start_time", "sci_status"],
267268
... sort_by=['sci_targname'])
268269
>>> results[:5] # doctest: +IGNORE_OUTPUT
269270
<Table masked=True length=5>

0 commit comments

Comments
 (0)