Skip to content

Commit 9202f73

Browse files
author
C. E. Brasseur
authored
Merge pull request #1988 from integral-observatory/master
Add alternative server with the same interface as HEASARC, but different tables (more recent data of INTEGRAL, some CTA tables)
2 parents 98e9537 + cf4fb8c commit 9202f73

File tree

6 files changed

+377
-31
lines changed

6 files changed

+377
-31
lines changed

CHANGES.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ New Tools and Services
88
Service fixes and enhancements
99
------------------------------
1010

11+
heasarc
12+
^^^^^^^
13+
14+
- Added posibility to query limited time range. [#1988]
15+
- Add alternative instance of HEASARC Server, maintained by
16+
INTEGRAL Science Data Center. [#1988]
17+
18+
19+
Service fixes and enhancements
20+
------------------------------
21+
1122

1223

1324
0.4.2 (2021-05-14)

astroquery/heasarc/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ class Conf(_config.ConfigNamespace):
1919
"""
2020

2121
server = _config.ConfigItem(
22-
['https://heasarc.gsfc.nasa.gov/db-perl/W3Browse/w3query.pl'],
22+
['https://heasarc.gsfc.nasa.gov/db-perl/W3Browse/w3query.pl',
23+
'https://www.isdc.unige.ch/browse/w3query.pl'],
2324
'Name of the HEASARC server used to query available missions.')
2425

2526
timeout = _config.ConfigItem(

astroquery/heasarc/core.py

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

33
import warnings
4-
from six import BytesIO
4+
from six import StringIO, BytesIO
55
from astropy.table import Table
66
from astropy.io import fits
77
from astropy import coordinates
@@ -29,11 +29,15 @@ class HeasarcClass(BaseQuery):
2929
TIMEOUT = conf.timeout
3030
coord_systems = ['fk5', 'fk4', 'equatorial', 'galactic']
3131

32-
def query_async(self, request_payload, cache=True, url=conf.server):
32+
def query_async(self, request_payload, cache=True, url=None):
3333
"""
3434
Submit a query based on a given request_payload. This allows detailed
3535
control of the query to be submitted.
3636
"""
37+
38+
if url is None:
39+
url = conf.server
40+
3741
response = self._request('GET', url, params=request_payload,
3842
timeout=self.TIMEOUT, cache=cache)
3943
return response
@@ -43,7 +47,7 @@ def query_mission_list(self, cache=True, get_query_payload=False):
4347
Returns a list of all available mission tables with descriptions
4448
"""
4549
request_payload = self._args_to_payload(
46-
Entry='none',
50+
entry='none',
4751
mission='xxx',
4852
displaymode='BatchDisplay'
4953
)
@@ -57,8 +61,7 @@ def query_mission_list(self, cache=True, get_query_payload=False):
5761
url=conf.server,
5862
cache=cache
5963
)
60-
data = BytesIO(response.content)
61-
data_str = data.read().decode('utf-8')
64+
data_str = response.text
6265
data_str = data_str.replace('Table xxx does not seem to exist!\n\n\n\nAvailable tables:\n', '')
6366
table = Table.read(data_str, format='ascii.fixed_width_two_line',
6467
delimiter='+', header_start=1, position_line=2,
@@ -82,19 +85,12 @@ def query_mission_cols(self, mission, cache=True, get_query_payload=False,
8285
* <custom> : User defined csv list of columns to be returned
8386
All other parameters have no effect
8487
"""
85-
# Query fails if nothing is found, so set search radius very large and
86-
# only take a single value (all we care about is the column names)
87-
kwargs['resultmax'] = 1
88-
89-
# By default, return all column names
90-
fields = kwargs.get('fields', None)
91-
if fields is None:
92-
kwargs['fields'] = 'All'
9388

9489
response = self.query_region_async(position='0.0 0.0', mission=mission,
9590
radius='361 degree', cache=cache,
9691
get_query_payload=get_query_payload,
97-
**kwargs)
92+
resultsmax=1,
93+
fields='All')
9894

9995
# Return payload if requested
10096
if get_query_payload:
@@ -177,20 +173,38 @@ def query_region_async(self, position, mission, radius,
177173
# Submit the request
178174
return self.query_async(request_payload, cache=cache)
179175

180-
def _fallback(self, content):
176+
def _old_w3query_fallback(self, content):
177+
# old w3query (such as that used in ISDC) return very strange fits, with all ints
178+
179+
f = fits.open(BytesIO(content))
180+
181+
for c in f[1].columns:
182+
if c.disp is not None:
183+
c.format = c.disp
184+
else:
185+
c.format = str(c.format).replace("I", "A")
186+
187+
I = BytesIO()
188+
f.writeto(I)
189+
I.seek(0)
190+
191+
return Table.read(I)
192+
193+
def _fallback(self, text):
181194
"""
182195
Blank columns which have to be converted to float or in fail so
183196
lets fix that by replacing with -1's
184197
"""
185198

186-
data = BytesIO(content)
199+
data = StringIO(text)
187200
header = fits.getheader(data, 1) # Get header for column info
188201
colstart = [y for x, y in header.items() if "TBCOL" in x]
189202
collens = [int(float(y[1:]))
190203
for x, y in header.items() if "TFORM" in x]
204+
191205
new_table = []
192206

193-
old_table = content.split("END")[-1].strip()
207+
old_table = text.split("END")[-1].strip()
194208
for line in old_table.split("\n"):
195209
newline = []
196210
for n, tup in enumerate(zip(colstart, collens), start=1):
@@ -199,11 +213,11 @@ def _fallback(self, content):
199213
newline.append(part)
200214
if len(part.strip()) == 0:
201215
if header["TFORM%i" % n][0] in ["F", "I"]:
202-
# extra space is required to sperate column
216+
# extra space is required to separate column
203217
newline[-1] = "-1".rjust(clen) + " "
204218
new_table.append("".join(newline))
205219

206-
data = BytesIO(content.replace(old_table, "\n".join(new_table)))
220+
data = StringIO(text.replace(old_table, "\n".join(new_table)))
207221
return Table.read(data, hdu=1)
208222

209223
def _parse_result(self, response, verbose=False):
@@ -228,7 +242,10 @@ def _parse_result(self, response, verbose=False):
228242
table = Table.read(data, hdu=1)
229243
return table
230244
except ValueError:
231-
return self._fallback(response.content)
245+
try:
246+
return self._fallback(response.text)
247+
except Exception as e:
248+
return self._old_w3query_fallback(response.content)
232249

233250
def _args_to_payload(self, **kwargs):
234251
"""
@@ -277,19 +294,27 @@ def _args_to_payload(self, **kwargs):
277294
action : str, optional
278295
Type of action to be taken (defaults to 'Query')
279296
"""
297+
# User-facing parameters are lower case, while parameters as passed to the HEASARC service are capitalized according to the HEASARC requirements.
298+
# The necessary transformations are done in this function.
299+
280300
# Define the basic query for this object
301+
mission = kwargs.pop('mission')
302+
281303
request_payload = dict(
282304
tablehead=('name=BATCHRETRIEVALCATALOG_2.0 {}'
283-
.format(kwargs.get('mission'))),
284-
Entry=kwargs.get('entry', 'none'),
285-
Action=kwargs.get('action', 'Query'),
286-
displaymode=kwargs.get('displaymode', 'FitsDisplay')
305+
.format(mission)),
306+
Entry=kwargs.pop('entry', 'none'),
307+
Action=kwargs.pop('action', 'Query'),
308+
displaymode=kwargs.pop('displaymode', 'FitsDisplay'),
309+
resultsmax=kwargs.pop('resultsmax', '10')
287310
)
288311

289312
# Fill in optional information for refined queries
290313

291314
# Handle queries involving coordinates
292-
coordsys = kwargs.get('coordsys', 'fk5')
315+
coordsys = kwargs.pop('coordsys', 'fk5')
316+
equinox = kwargs.pop('equinox', None)
317+
293318
if coordsys.lower() == 'fk5':
294319
request_payload['Coordinates'] = 'Equatorial: R.A. Dec'
295320

@@ -300,7 +325,6 @@ def _args_to_payload(self, **kwargs):
300325
elif coordsys.lower() == 'equatorial':
301326
request_payload['Coordinates'] = 'Equatorial: R.A. Dec'
302327

303-
equinox = kwargs.get('equinox', None)
304328
if equinox is not None:
305329
request_payload['Equinox'] = str(equinox)
306330

@@ -312,7 +336,7 @@ def _args_to_payload(self, **kwargs):
312336
.format(self.coord_systems))
313337

314338
# Specify which table columns are to be returned
315-
fields = kwargs.get('fields', None)
339+
fields = kwargs.pop('fields', None)
316340
if fields is not None:
317341
if fields.lower() == 'standard':
318342
request_payload['Fields'] = 'Standard'
@@ -322,20 +346,37 @@ def _args_to_payload(self, **kwargs):
322346
request_payload['varon'] = fields.lower().split(',')
323347

324348
# Set search radius (arcmin)
325-
radius = kwargs.get('radius', None)
349+
radius = kwargs.pop('radius', None)
326350
if radius is not None:
327351
request_payload['Radius'] = "{}".format(u.Quantity(radius).to(u.arcmin))
328352

329353
# Maximum number of results to be returned
330-
resultmax = kwargs.get('resultmax', None)
354+
resultmax = kwargs.pop('resultmax', None)
331355
if resultmax is not None:
332356
request_payload['ResultMax'] = int(resultmax)
333357

334358
# Set variable for sorting results
335-
sortvar = kwargs.get('sortvar', None)
359+
sortvar = kwargs.pop('sortvar', None)
336360
if sortvar is not None:
337361
request_payload['sortvar'] = sortvar.lower()
338362

363+
# Time range variable
364+
_time = kwargs.pop('time', None)
365+
if _time is not None:
366+
request_payload['Time'] = _time
367+
368+
if len(kwargs) > 0:
369+
mission_fields = [k.lower() for k in self.query_mission_cols(mission=mission)]
370+
371+
for k, v in kwargs.items():
372+
if k.lower() in mission_fields:
373+
request_payload['bparam_' + k.lower()] = v
374+
else:
375+
raise ValueError("unknown parameter '{}' provided, must be one of {!s}".format(
376+
k,
377+
mission_fields,
378+
))
379+
339380
return request_payload
340381

341382

astroquery/heasarc/tests/test_heasarc_remote.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,34 @@
1010

1111
@pytest.mark.remote_data
1212
class TestHeasarc:
13+
def test_custom_args(self):
14+
object_name = 'Crab'
15+
mission = 'intscw'
16+
17+
heasarc = Heasarc()
18+
19+
table = heasarc.query_object(object_name,
20+
mission=mission,
21+
radius='1 degree',
22+
time="2020-09-01 .. 2020-12-01",
23+
resultmax=10,
24+
good_isgri=">1000",
25+
)
26+
27+
def test_filter_custom_args(self):
28+
object_name = 'Crab'
29+
mission = 'intscw'
30+
31+
heasarc = Heasarc()
32+
33+
with pytest.raises(ValueError):
34+
table = heasarc.query_object(object_name,
35+
mission=mission,
36+
radius='1 degree',
37+
time="2020-09-01 .. 2020-12-01",
38+
resultmax=10,
39+
very_good_isgri=">1000",
40+
)
1341

1442
def test_basic_example(self):
1543
mission = 'rosmaster'

0 commit comments

Comments
 (0)