Skip to content

Commit f34d27c

Browse files
committed
Merge pull request #696 from keflavich/eso_updates_for_remote_api_change
ESO updates for remote API changes
2 parents b3e5fe1 + 7a141ee commit f34d27c

File tree

4 files changed

+227
-70
lines changed

4 files changed

+227
-70
lines changed

CHANGES

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
0.3.2.dev
22
---------
33

4+
- Update ESO tool to work with new web API (#696)
45
- Added new instruments for ESO: ``ambient_paranal`` and ``meteo_paranal``
56
(#657)
67
- Fix problem with listed votable fields being truncated in SIMBAD (#654)

astroquery/eso/core.py

Lines changed: 186 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -99,21 +99,39 @@ def _activate_form(self, response, form_index=0, inputs={}, cache=True,
9999
elif tag_name == 'select':
100100
if form_elem.get('multiple') is not None:
101101
value = []
102-
for option in form_elem.select('option[value]'):
103-
if option.get('selected') is not None:
104-
value.append(option.get('value'))
102+
if form_elem.select('option[value]'):
103+
for option in form_elem.select('option[value]'):
104+
if option.get('selected') is not None:
105+
value.append(option.get('value'))
106+
else:
107+
for option in form_elem.select('option'):
108+
if option.get('selected') is not None:
109+
value.append(option.string)
105110
else:
106-
for option in form_elem.select('option[value]'):
107-
if option.get('selected') is not None:
108-
value = option.get('value')
109-
# select the first option field if none is selected
110-
if value is None:
111-
value = form_elem.select(
112-
'option[value]')[0].get('value')
111+
if form_elem.select('option[value]'):
112+
for option in form_elem.select('option[value]'):
113+
if option.get('selected') is not None:
114+
value = option.get('value')
115+
# select the first option field if none is selected
116+
if value is None:
117+
value = form_elem.select(
118+
'option[value]')[0].get('value')
119+
else:
120+
# survey form just uses text, not value
121+
for option in form_elem.select('option'):
122+
if option.get('selected') is not None:
123+
value = option.string
124+
# select the first option field if none is selected
125+
if value is None:
126+
value = form_elem.select('option')[0].string
113127

114128
if key in inputs:
115-
value = str(inputs[key])
116-
if (key is not None) and (value is not None):
129+
if isinstance(inputs[key], list):
130+
# list input is accepted (for array uploads)
131+
value = inputs[key]
132+
else:
133+
value = str(inputs[key])
134+
if (key is not None):# and (value is not None):
117135
if fmt == 'multipart/form-data':
118136
if is_file:
119137
payload.append(
@@ -122,6 +140,8 @@ def _activate_form(self, response, form_index=0, inputs={}, cache=True,
122140
if type(value) is list:
123141
for v in value:
124142
payload.append((key, ('', v)))
143+
elif value is None:
144+
payload.append((key, ('', '')))
125145
else:
126146
payload.append((key, ('', value)))
127147
else:
@@ -138,6 +158,8 @@ def _activate_form(self, response, form_index=0, inputs={}, cache=True,
138158
if method is not None:
139159
fmt = method
140160

161+
log.debug("Method/format = {0}".format(fmt))
162+
141163
# Send payload
142164
if fmt == 'get':
143165
response = self._request("GET", url, params=payload, cache=cache)
@@ -148,16 +170,37 @@ def _activate_form(self, response, form_index=0, inputs={}, cache=True,
148170

149171
return response
150172

151-
def _login(self, username=None, store_password=False):
173+
def _login(self, username=None, store_password=False,
174+
retype_password=False):
175+
"""
176+
Login to the ESO User Portal.
177+
178+
Parameters
179+
----------
180+
username : str, optional
181+
Username to the ESO Public Portal. If not given, it should be
182+
specified in the config file.
183+
store_password : bool, optional
184+
Stores the password securely in your keyring. Default is False.
185+
retype_password : bool, optional
186+
Asks for the password even if it is already stored in the
187+
keyring. This is the way to overwrite an already stored passwork
188+
on the keyring. Default is False.
189+
"""
152190
if username is None:
153191
if self.USERNAME == "":
154192
raise LoginError("If you do not pass a username to login(), "
155193
"you should configure a default one!")
156194
else:
157195
username = self.USERNAME
196+
158197
# Get password from keyring or prompt
159-
password_from_keyring = keyring.get_password("astroquery:www.eso.org",
160-
username)
198+
if retype_password is False:
199+
password_from_keyring = keyring.get_password(
200+
"astroquery:www.eso.org", username)
201+
else:
202+
password_from_keyring = None
203+
161204
if password_from_keyring is None:
162205
if system_tools.in_ipynb():
163206
log.warn("You may be using an ipython notebook:"
@@ -207,8 +250,6 @@ def list_instruments(self, cache=True):
207250
if instrument not in self._instrument_list:
208251
self._instrument_list.append(instrument)
209252
self._instrument_list.append(u'harps')
210-
self._instrument_list.append(u'ambient_paranal')
211-
self._instrument_list.append(u'meteo_paranal')
212253
return self._instrument_list
213254

214255
def list_surveys(self, cache=True):
@@ -219,30 +260,34 @@ def list_surveys(self, cache=True):
219260
survey_list : list of strings
220261
cache : bool
221262
Cache the response for faster subsequent retrieval
222-
223263
"""
224264
if self._survey_list is None:
225265
survey_list_response = self._request(
226266
"GET", "http://archive.eso.org/wdb/wdb/adp/phase3_main/form",
227267
cache=cache)
228-
root = BeautifulSoup(survey_list_response .content, 'html5lib')
268+
root = BeautifulSoup(survey_list_response.content, 'html5lib')
229269
self._survey_list = []
230-
for select in root.select("select[name=phase3_program]"):
231-
for element in select.select('option'):
232-
survey = element.text.strip()
233-
if survey not in self._survey_list and 'Any' not in survey:
270+
collections_table = root.find('table', id='collections_table')
271+
other_collections = root.find('select', id='collection_name_option')
272+
for element in (collections_table.findAll('input', type='checkbox') +
273+
other_collections.findAll('option')):
274+
if 'value' in element.attrs:
275+
survey = element.attrs['value']
276+
if survey and survey not in self._survey_list and 'Any' not in survey:
234277
self._survey_list.append(survey)
235278
return self._survey_list
236279

237-
def query_survey(self, survey, cache=True, **kwargs):
280+
def query_surveys(self, surveys='', cache=True,
281+
help=False, open_form=False, **kwargs):
238282
"""
239283
Query survey Phase 3 data contained in the ESO archive.
240284
241285
Parameters
242286
----------
243-
survey : string
244-
Name of the survey to query, one of the names returned by
245-
`list_surveys()`.
287+
survey : string or list
288+
Name of the survey(s) to query. Should beone or more of the names
289+
returned by `list_surveys()`. If specified as a string, should be
290+
a comma-separated list of survey names.
246291
cache : bool
247292
Cache the response for faster subsequent retrieval
248293
@@ -257,30 +302,37 @@ def query_survey(self, survey, cache=True, **kwargs):
257302
258303
"""
259304

260-
if survey not in self.list_surveys():
261-
raise ValueError("Survey %s is not in the survey list." % survey)
262305
url = "http://archive.eso.org/wdb/wdb/adp/phase3_main/form"
263-
survey_form = self._request("GET", url, cache=cache)
264-
query_dict = kwargs
265-
query_dict["wdbo"] = "csv/download"
266-
query_dict['phase3_program'] = survey
267-
if self.ROW_LIMIT >= 0:
268-
query_dict["max_rows_returned"] = self.ROW_LIMIT
269-
else:
270-
query_dict["max_rows_returned"] = 10000
271-
survey_response = self._activate_form(survey_form, form_index=0,
272-
inputs=query_dict, cache=cache)
273-
274-
content = survey_response.content
275-
# First line is always garbage
276-
content = content.split(b'\n', 1)[1]
277-
log.debug("Response content:\n{0}".format(content))
278-
if _check_response(content):
279-
table = Table.read(BytesIO(content), format="ascii.csv",
280-
comment="^#")
281-
return table
306+
if open_form:
307+
webbrowser.open(url)
308+
elif help:
309+
self._print_surveys_help(url, cache=cache)
282310
else:
283-
warnings.warn("Query returned no results", NoResultsWarning)
311+
survey_form = self._request("GET", url, cache=cache)
312+
query_dict = kwargs
313+
query_dict["wdbo"] = "csv/download"
314+
if isinstance(surveys, six.string_types):
315+
surveys = surveys.split(",")
316+
query_dict['collection_name'] = surveys
317+
if self.ROW_LIMIT >= 0:
318+
query_dict["max_rows_returned"] = self.ROW_LIMIT
319+
else:
320+
query_dict["max_rows_returned"] = 10000
321+
322+
survey_response = self._activate_form(survey_form, form_index=0,
323+
inputs=query_dict, cache=cache)
324+
325+
content = survey_response.content
326+
# First line is always garbage
327+
content = content.split(b'\n', 1)[1]
328+
log.debug("Response content:\n{0}".format(content))
329+
if _check_response(content):
330+
table = Table.read(BytesIO(content), format="ascii.csv",
331+
comment="^#")
332+
return table
333+
else:
334+
warnings.warn("Query returned no results", NoResultsWarning)
335+
284336

285337
def query_instrument(self, instrument, column_filters={}, columns=[],
286338
open_form=False, help=False, cache=True, **kwargs):
@@ -326,7 +378,7 @@ def query_instrument(self, instrument, column_filters={}, columns=[],
326378
if open_form:
327379
webbrowser.open(url)
328380
elif help:
329-
self._print_help(url, instrument)
381+
self._print_instrument_help(url, instrument)
330382
else:
331383
instrument_form = self._request("GET", url, cache=cache)
332384
query_dict = {}
@@ -599,7 +651,7 @@ def query_apex_quicklooks(self, project_id=None, help=False,
599651
if open_form:
600652
webbrowser.open(apex_query_url)
601653
elif help:
602-
return self._print_help(apex_query_url, 'apex')
654+
return self._print_instrument_help(apex_query_url, 'apex')
603655
else:
604656

605657
payload = {'wdbo': 'csv/download'}
@@ -624,7 +676,7 @@ def query_apex_quicklooks(self, project_id=None, help=False,
624676

625677
return table
626678

627-
def _print_help(self, url, instrument, cache=True):
679+
def _print_instrument_help(self, url, instrument, cache=True):
628680
"""
629681
Download a form and print it in a quasi-human-readable way
630682
"""
@@ -681,4 +733,86 @@ def _print_help(self, url, instrument, cache=True):
681733
print("\n".join(result_string))
682734
return result_string
683735

736+
def query_survey(self, **kwargs):
737+
raise DeprecationWarning("query_survey is deprecated; use "
738+
"query_surveys instead. It should "
739+
"accept the same arguments.")
740+
741+
def _print_surveys_help(self, url, cache=True):
742+
"""
743+
Download a form and print it in a quasi-human-readable way
744+
"""
745+
log.info("List of the parameters accepted by the "
746+
"surveys query.")
747+
log.info("The presence of a column in the result table can be "
748+
"controlled if prefixed with a [ ] checkbox.")
749+
log.info("The default columns in the result table are shown as "
750+
"already ticked: [x].")
751+
752+
result_string = []
753+
754+
resp = self._request("GET", url, cache=cache)
755+
doc = BeautifulSoup(resp.content, 'html5lib')
756+
form = doc.select("html body form")[0]
757+
758+
# hovertext from different labels are used to give more info on forms
759+
helptext_dict = {abbr['title'].split(":")[0].strip():
760+
":".join(abbr['title'].split(":")[1:])
761+
for abbr in form.findAll('abbr')
762+
if 'title' in abbr.attrs and ":" in abbr['title']}
763+
764+
for fieldset in form.select('fieldset'):
765+
legend = fieldset.select('legend')
766+
if len(legend) > 1:
767+
raise ValueError("Form parsing error: too many legends.")
768+
elif len(legend) == 0:
769+
continue
770+
section_title = "\n\n"+"".join(legend[0].stripped_strings)+"\n"
771+
772+
result_string.append(section_title)
773+
774+
for section in fieldset.select('table'):
775+
776+
checkbox_name = ""
777+
checkbox_value = ""
778+
for tag in section.next_elements:
779+
if tag.name == u"table":
780+
break
781+
elif tag.name == u"input":
782+
if tag.get(u'type') == u"checkbox":
783+
checkbox_name = tag['name']
784+
checkbox_value = (u"[x]"
785+
if ('checked' in tag.attrs)
786+
else u"[ ]")
787+
name = ""
788+
value = ""
789+
else:
790+
name = tag['name']
791+
value = ""
792+
elif tag.name == u"select":
793+
options = []
794+
for option in tag.select("option"):
795+
options += ["{0} ({1})"
796+
.format(option['value']
797+
if 'value' in option
798+
else "",
799+
"".join(option.stripped_strings))]
800+
name = tag[u"name"]
801+
value = ", ".join(options)
802+
else:
803+
name = ""
804+
value = ""
805+
if u"tab_" + name == checkbox_name:
806+
checkbox = checkbox_value
807+
else:
808+
checkbox = " "
809+
if name != u"":
810+
result_string.append("{0} {1}: {2}"
811+
.format(checkbox, name, value))
812+
if name.strip() in helptext_dict:
813+
result_string.append(helptext_dict[name.strip()])
814+
815+
print("\n".join(result_string))
816+
return result_string
817+
684818
Eso = EsoClass()

astroquery/eso/tests/test_eso.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Licensed under a 3-clause BSD style license - see LICENSE.rst
22
import os
3-
from astropy.tests.helper import pytest
43
from ...utils.testing_tools import MockResponse
54

65
from ...eso import Eso
@@ -62,8 +61,10 @@ def test_vvv(monkeypatch):
6261
monkeypatch.setattr(eso, '_request', eso_request)
6362
eso.cache_location = DATA_DIR
6463

65-
result_s = eso.query_survey('VVV',
66-
coord1=266.41681662, coord2=-29.00782497)
64+
result_s = eso.query_surveys('VVV',
65+
coord1=266.41681662, coord2=-29.00782497,
66+
box='01 00 00',
67+
)
6768
assert result_s is not None
6869
assert 'Object' in result_s.colnames
6970
assert 'b333' in result_s['Object']

0 commit comments

Comments
 (0)