Skip to content

Commit 011e4f4

Browse files
committed
Resolve multiple objects at once, tests
1 parent c4ed1ac commit 011e4f4

File tree

7 files changed

+378
-102
lines changed

7 files changed

+378
-102
lines changed

astroquery/mast/missions.py

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from astroquery import log
2222
from astroquery.utils import commons, async_to_sync
2323
from astroquery.utils.class_or_instance import class_or_instance
24-
from astropy.utils.console import ProgressBarOrSpinner
2524
from astroquery.exceptions import InvalidQueryError, MaxResultsWarning, NoResultsWarning
2625

2726
from astroquery.mast import utils
@@ -439,31 +438,16 @@ def get_product_list_async(self, datasets):
439438
# Filter out duplicates
440439
datasets = list(set(datasets))
441440

442-
# Batch API calls if number of datasets exceeds maximum
443-
max_batch = 1000
444-
num_datasets = len(datasets)
445-
if num_datasets > max_batch:
446-
# Split datasets into chunks
447-
dataset_chunks = list(utils.split_list_into_chunks(datasets, max_batch))
448-
449-
results = [] # list to store responses from each batch
450-
with ProgressBarOrSpinner(num_datasets, f'Fetching products for {num_datasets} unique datasets '
451-
f'in {len(dataset_chunks)} batches ...') as pb:
452-
datasets_fetched = 0
453-
pb.update(0)
454-
for chunk in dataset_chunks:
455-
# Send request for each chunk and add response to list
456-
params = {'dataset_ids': chunk}
457-
results.append(self._service_api_connection.missions_request_async(self.service, params))
458-
459-
# Update progress bar with the number of datasets that have had products fetched
460-
datasets_fetched += len(chunk)
461-
pb.update(datasets_fetched)
462-
return results
463-
else:
464-
# Single batch request
465-
params = {'dataset_ids': datasets}
466-
return self._service_api_connection.missions_request_async(self.service, params)
441+
results = utils._batched_request(
442+
datasets,
443+
params={},
444+
max_batch=1000,
445+
param_key="dataset_ids",
446+
request_func=lambda p: self._service_api_connection.missions_request_async(self.service, p),
447+
extract_func=lambda r: r, # missions_request_async already returns one result
448+
desc=f"Fetching products for {len(datasets)} unique datasets"
449+
)
450+
return results
467451

468452
def get_unique_product_list(self, datasets):
469453
"""

astroquery/mast/services.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ def _json_to_table(json_obj, data_key='data'):
102102
col_mask = np.equal(col_data, ignore_value)
103103

104104
# add the column
105-
data_table.add_column(MaskedColumn(col_data.astype(col_type), name=col_name, mask=col_mask))
105+
if col_name not in data_table.colnames:
106+
data_table.add_column(MaskedColumn(col_data.astype(col_type), name=col_name, mask=col_mask))
106107

107108
return data_table
108109

astroquery/mast/tests/data/README.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ To generate `~astroquery.mast.tests.data.resolver.json`, use the following:
5757
>>> import json
5858
>>> from astroquery.mast import utils
5959
...
60+
>>> objects = ["TIC 307210830", "Barnard's Star", "M1", "M101", "M103", "M8", "M10"]
6061
>>> resp = utils._simple_request('http://mastresolver.stsci.edu/Santa-war/query',
61-
... {'name': 'TIC 307210830', 'outputFormat': 'json', 'resolveAll': 'true'})
62+
... {'name': objects, 'outputFormat': 'json', 'resolveAll': 'true'})
6263
>>> with open('resolver.json', 'w') as file:
6364
... json.dump(resp.json(), file, indent=4) # doctest: +SKIP

astroquery/mast/tests/data/resolver.json

Lines changed: 164 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,18 @@
44
"searchString": "tic 307210830",
55
"resolver": "TIC",
66
"cached": false,
7-
"resolverTime": 3,
7+
"resolverTime": 2,
88
"searchRadius": 0.000333,
99
"canonicalName": "TIC 307210830",
1010
"ra": 124.531756290083,
1111
"decl": -68.3129998725044
1212
},
1313
{
1414
"searchString": "tic 307210830",
15-
"resolver": "SIMBADCFA",
15+
"resolver": "SIMBAD",
1616
"cached": true,
17-
"resolverTime": 289,
18-
"cacheDate": "Mar 19, 2025, 4:36:27 PM",
17+
"resolverTime": 294,
18+
"cacheDate": "Apr 17, 2025, 3:47:59 PM",
1919
"searchRadius": -1.0,
2020
"canonicalName": "L 98-59",
2121
"ra": 124.5317560026638,
@@ -24,15 +24,172 @@
2424
},
2525
{
2626
"searchString": "tic 307210830",
27-
"resolver": "SIMBAD",
27+
"resolver": "SIMBADCFA",
2828
"cached": true,
29-
"resolverTime": 299,
30-
"cacheDate": "Apr 17, 2025, 3:47:59 PM",
29+
"resolverTime": 296,
30+
"cacheDate": "Mar 19, 2025, 4:36:27 PM",
3131
"searchRadius": -1.0,
3232
"canonicalName": "L 98-59",
3333
"ra": 124.5317560026638,
3434
"decl": -68.3130014904408,
3535
"objectType": "HighPM*"
36+
},
37+
{
38+
"searchString": "barnard's star",
39+
"resolver": "SIMBAD",
40+
"cached": true,
41+
"resolverTime": 302,
42+
"cacheDate": "May 7, 2025, 2:29:06 PM",
43+
"searchRadius": -1.0,
44+
"canonicalName": "NAME Barnard's star",
45+
"ra": 269.4520769586187,
46+
"decl": 4.6933649665767,
47+
"objectType": "BYDraV*"
48+
},
49+
{
50+
"searchString": "m1",
51+
"resolver": "SIMBAD",
52+
"cached": true,
53+
"resolverTime": 292,
54+
"cacheDate": "Apr 29, 2025, 1:57:27 PM",
55+
"searchRadius": -1.0,
56+
"canonicalName": "M 1",
57+
"ra": 83.6324,
58+
"decl": 22.0174,
59+
"radius": 0.058333333333333334,
60+
"majorAxis": 0.11666666666666667,
61+
"minorAxis": 0.08333333333333333,
62+
"objectType": "SNRemnant"
63+
},
64+
{
65+
"searchString": "m1",
66+
"resolver": "NED",
67+
"cached": false,
68+
"resolverTime": 1417,
69+
"searchRadius": -1.0,
70+
"canonicalName": "MESSIER 001",
71+
"ra": 83.63311,
72+
"decl": 22.01449,
73+
"objectType": "SNR"
74+
},
75+
{
76+
"searchString": "m101",
77+
"resolver": "SIMBAD",
78+
"cached": true,
79+
"resolverTime": 290,
80+
"cacheDate": "Sep 2, 2025, 8:13:51 PM",
81+
"searchRadius": -1.0,
82+
"canonicalName": "M 101",
83+
"ra": 210.802429,
84+
"decl": 54.34875,
85+
"radius": 0.18233333333333332,
86+
"majorAxis": 0.36466666666666664,
87+
"minorAxis": 0.3481666666666667,
88+
"objectType": "GinPair"
89+
},
90+
{
91+
"searchString": "m101",
92+
"resolver": "SIMBADCFA",
93+
"cached": true,
94+
"resolverTime": 289,
95+
"cacheDate": "May 14, 2025, 8:04:05 PM",
96+
"searchRadius": -1.0,
97+
"canonicalName": "M 101",
98+
"ra": 210.802429,
99+
"decl": 54.34875,
100+
"radius": 0.18233333333333332,
101+
"majorAxis": 0.36466666666666664,
102+
"minorAxis": 0.3481666666666667,
103+
"objectType": "GinPair"
104+
},
105+
{
106+
"searchString": "m101",
107+
"resolver": "NED",
108+
"cached": false,
109+
"resolverTime": 1181,
110+
"searchRadius": -1.0,
111+
"canonicalName": "MESSIER 101",
112+
"ra": 210.80227,
113+
"decl": 54.34895,
114+
"radius": 0.24000000000000002,
115+
"objectType": "G"
116+
},
117+
{
118+
"searchString": "m103",
119+
"resolver": "SIMBAD",
120+
"cached": true,
121+
"resolverTime": 294,
122+
"cacheDate": "May 2, 2025, 4:33:25 AM",
123+
"searchRadius": -1.0,
124+
"canonicalName": "M 103",
125+
"ra": 23.339,
126+
"decl": 60.659,
127+
"radius": 0.06166666666666667,
128+
"majorAxis": 0.12333333333333334,
129+
"minorAxis": 0.12333333333333334,
130+
"positionAngle": 0.0,
131+
"objectType": "OpenCluster"
132+
},
133+
{
134+
"searchString": "m103",
135+
"resolver": "NED",
136+
"cached": false,
137+
"resolverTime": 1094,
138+
"searchRadius": -1.0,
139+
"canonicalName": "MESSIER 103",
140+
"ra": 23.34086,
141+
"decl": 60.658,
142+
"objectType": "*Cl"
143+
},
144+
{
145+
"searchString": "m8",
146+
"resolver": "SIMBAD",
147+
"cached": true,
148+
"resolverTime": 294,
149+
"cacheDate": "May 1, 2025, 1:11:47 PM",
150+
"searchRadius": -1.0,
151+
"canonicalName": "M 8",
152+
"ra": 270.904,
153+
"decl": -24.387,
154+
"objectType": "OpenCluster"
155+
},
156+
{
157+
"searchString": "m8",
158+
"resolver": "NED",
159+
"cached": false,
160+
"resolverTime": 1165,
161+
"searchRadius": -1.0,
162+
"canonicalName": "MESSIER 008",
163+
"ra": 270.92194,
164+
"decl": -24.38017,
165+
"objectType": "Neb"
166+
},
167+
{
168+
"searchString": "m10",
169+
"resolver": "SIMBAD",
170+
"cached": true,
171+
"resolverTime": 290,
172+
"cacheDate": "Apr 29, 2025, 2:00:16 PM",
173+
"searchRadius": -1.0,
174+
"canonicalName": "M 10",
175+
"ra": 254.28771,
176+
"decl": -4.10031,
177+
"radius": 0.12583333333333332,
178+
"majorAxis": 0.25166666666666665,
179+
"minorAxis": 0.25166666666666665,
180+
"positionAngle": 90.0,
181+
"objectType": "GlobCluster"
182+
},
183+
{
184+
"searchString": "m10",
185+
"resolver": "NED",
186+
"cached": false,
187+
"resolverTime": 998,
188+
"searchRadius": -1.0,
189+
"canonicalName": "MESSIER 010",
190+
"ra": 254.28771,
191+
"decl": -4.10031,
192+
"objectType": "*Cl"
36193
}
37194
],
38195
"status": ""

astroquery/mast/tests/test_mast.py

Lines changed: 58 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -532,36 +532,86 @@ def test_mast_query(patch_post):
532532
assert "Please provide at least one filter." in str(invalid_query.value)
533533

534534

535-
def test_resolve_object(patch_post):
535+
def test_resolve_object_single(patch_post):
536536
obj = "TIC 307210830"
537537
tic_coord = SkyCoord(124.531756290083, -68.3129998725044, unit="deg")
538538
simbad_coord = SkyCoord(124.5317560026638, -68.3130014904408, unit="deg")
539+
540+
# Resolve without a specific resolver
539541
obj_loc = mast.Mast.resolve_object(obj)
542+
assert isinstance(obj_loc, SkyCoord)
540543
assert round(obj_loc.separation(tic_coord).value, 10) == 0
541544

542-
# resolve using a specific resolver and an object that belongs to a MAST catalog
545+
# Resolve using a specific resolver and an object that belongs to a MAST catalog
543546
obj_loc_simbad = mast.Mast.resolve_object(obj, resolver="SIMBAD")
544547
assert round(obj_loc_simbad.separation(simbad_coord).value, 10) == 0
545548

546-
# resolve using a specific resolver and an object that does not belong to a MAST catalog
547-
obj_loc_simbad = mast.Mast.resolve_object("M101", resolver="SIMBAD")
548-
assert round(obj_loc_simbad.separation(simbad_coord).value, 10) == 0
549+
# Resolve using a specific resolver and an object that does not belong to a MAST catalog
550+
m1_coord = SkyCoord(83.6324, 22.0174, unit="deg")
551+
obj_loc_simbad = mast.Mast.resolve_object("M1", resolver="SIMBAD")
552+
assert isinstance(obj_loc_simbad, SkyCoord)
553+
assert round(obj_loc_simbad.separation(m1_coord).value, 10) == 0
549554

550-
# resolve using all resolvers
555+
# Resolve using all resolvers
551556
obj_loc_dict = mast.Mast.resolve_object(obj, resolve_all=True)
552557
assert isinstance(obj_loc_dict, dict)
553558
assert round(obj_loc_dict["SIMBAD"].separation(simbad_coord).value, 10) == 0
559+
assert round(obj_loc_dict["TIC"].separation(tic_coord).value, 10) == 0
554560

555-
# error with invalid resolver
561+
# Error with invalid resolver
556562
with pytest.raises(ResolverError, match="Invalid resolver"):
557563
mast.Mast.resolve_object(obj, resolver="invalid")
558564

559-
# warn if specifying both resolver and resolve_all
565+
# Error if single object cannot be resolved
566+
with pytest.raises(ResolverError, match='Could not resolve "nonexisting" to a sky position.'):
567+
mast.Mast.resolve_object("nonexisting")
568+
569+
# Error if single object cannot be resolved with given resolver
570+
with pytest.raises(ResolverError, match='Could not resolve "Barnard\'s Star" to a sky position using '
571+
'resolver "NED".'):
572+
mast.Mast.resolve_object("Barnard's Star", resolver="NED")
573+
574+
# Warn if specifying both resolver and resolve_all
560575
with pytest.warns(InputWarning, match="The resolver parameter is ignored when resolve_all is True"):
561576
loc = mast.Mast.resolve_object(obj, resolver="NED", resolve_all=True)
562577
assert isinstance(loc, dict)
563578

564579

580+
def test_resolve_object_multi(patch_post):
581+
objects = ["TIC 307210830", "M1", "Barnard's Star"]
582+
583+
# No resolver specified
584+
coord_dict = mast.Mast.resolve_object(objects)
585+
assert isinstance(coord_dict, dict)
586+
for obj in objects:
587+
assert obj in coord_dict
588+
assert isinstance(coord_dict[obj], SkyCoord)
589+
590+
# Warn if one of the objects cannot be resolved
591+
with pytest.warns(InputWarning, match='Could not resolve "nonexisting" to a sky position.'):
592+
coord_dict = mast.Mast.resolve_object(["M1", "nonexisting"])
593+
594+
# Resolver specified
595+
coord_dict = mast.Mast.resolve_object(objects, resolver="SIMBAD")
596+
assert isinstance(coord_dict, dict)
597+
for obj in objects:
598+
assert obj in coord_dict
599+
assert isinstance(coord_dict[obj], SkyCoord)
600+
601+
# Warn if one of the objects can't be resolved with given resolver
602+
with pytest.warns(InputWarning, match='Could not resolve "TIC 307210830" to a sky position using resolver "NED"'):
603+
mast.Mast.resolve_object(objects[:2], resolver="NED")
604+
605+
# Resolve all
606+
coord_dict = mast.Mast.resolve_object(objects, resolve_all=True)
607+
assert isinstance(coord_dict, dict)
608+
for obj in objects:
609+
assert obj in coord_dict
610+
obj_dict = coord_dict[obj]
611+
assert isinstance(obj_dict, dict)
612+
assert isinstance(obj_dict["SIMBAD"], SkyCoord)
613+
614+
565615
def test_login_logout(patch_post):
566616
test_token = "56a9cf3df4c04052atest43feb87f282"
567617

astroquery/mast/tests/test_mast_remote.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,11 @@ def test_resolve_object(self):
7171
assert loc_dict['SIMBAD'] == simbad_loc
7272

7373
# Error if coordinates cannot be resolved
74-
with pytest.raises(ResolverError, match="Could not resolve invalid to a sky position."):
74+
with pytest.raises(ResolverError, match='Could not resolve "invalid" to a sky position.'):
7575
utils.resolve_object("invalid")
7676

7777
# Error if coordinates cannot be resolved with a specific resolver
78-
with pytest.raises(ResolverError, match="Could not resolve invalid to a sky position using NED"):
78+
with pytest.raises(ResolverError, match='Could not resolve "invalid" to a sky position using resolver "NED"'):
7979
utils.resolve_object("invalid", resolver="NED")
8080

8181
###########################

0 commit comments

Comments
 (0)