1
|
|
# Licensed under a 3-clause BSD style license - see LICENSE.rst
|
2
|
1
|
"""
|
3
|
|
Module to search the SAO/NASA Astrophysics Data System
|
4
|
|
|
5
|
|
:author: Magnus Persson <magnusp@vilhelm.nu>
|
6
|
|
|
7
|
|
"""
|
8
|
1
|
import os
|
9
|
|
|
10
|
1
|
from astropy.table import Table
|
11
|
1
|
from six.moves.urllib.parse import quote as urlencode
|
12
|
|
|
13
|
1
|
from ..query import BaseQuery
|
14
|
1
|
from ..utils import async_to_sync
|
15
|
1
|
from ..utils.class_or_instance import class_or_instance
|
16
|
|
|
17
|
1
|
from .utils import _get_data_from_xml
|
18
|
1
|
from . import conf
|
19
|
|
|
20
|
|
|
21
|
1
|
from xml.dom import minidom
|
22
|
|
|
23
|
1
|
__all__ = ['ADS', 'ADSClass']
|
24
|
|
|
25
|
|
|
26
|
1
|
@async_to_sync
|
27
|
1
|
class ADSClass(BaseQuery):
|
28
|
|
|
29
|
1
|
SERVER = conf.server
|
30
|
1
|
QUERY_SIMPLE_PATH = conf.simple_path
|
31
|
1
|
TIMEOUT = conf.timeout
|
32
|
1
|
ADS_FIELDS = conf.adsfields
|
33
|
1
|
SORT = conf.sort
|
34
|
1
|
NROWS = conf.nrows
|
35
|
1
|
NSTART = conf.nstart
|
36
|
1
|
TOKEN = conf.token
|
37
|
|
|
38
|
1
|
QUERY_SIMPLE_URL = SERVER + QUERY_SIMPLE_PATH
|
39
|
|
|
40
|
1
|
def __init__(self, *args):
|
41
|
|
""" set some parameters """
|
42
|
1
|
super(ADSClass, self).__init__()
|
43
|
|
|
44
|
1
|
@class_or_instance
|
45
|
1
|
def query_simple(self, query_string, get_query_payload=False,
|
46
|
|
get_raw_response=False, cache=True):
|
47
|
|
"""
|
48
|
|
Basic query. Uses a string and the ADS generic query.
|
49
|
|
"""
|
50
|
1
|
request_string = self._args_to_url(query_string)
|
51
|
1
|
request_fields = self._fields_to_url()
|
52
|
1
|
request_sort = self._sort_to_url()
|
53
|
1
|
request_rows = self._rows_to_url(self.NROWS, self.NSTART)
|
54
|
1
|
request_url = self.QUERY_SIMPLE_URL + request_string + request_fields + request_sort + request_rows
|
55
|
|
|
56
|
|
# primarily for debug purposes, but also useful if you want to send
|
57
|
|
# someone a URL linking directly to the data
|
58
|
1
|
if get_query_payload:
|
59
|
1
|
return request_url
|
60
|
|
|
61
|
1
|
response = self._request(method='GET', url=request_url,
|
62
|
|
headers={'Authorization': 'Bearer ' + self._get_token()},
|
63
|
|
timeout=self.TIMEOUT, cache=cache)
|
64
|
|
|
65
|
1
|
response.raise_for_status()
|
66
|
|
|
67
|
1
|
if get_raw_response:
|
68
|
0
|
return response
|
69
|
|
# parse the XML response into AstroPy Table
|
70
|
1
|
resulttable = self._parse_response(response.json())
|
71
|
|
|
72
|
1
|
return resulttable
|
73
|
|
|
74
|
1
|
def _parse_response(self, response):
|
75
|
|
|
76
|
1
|
try:
|
77
|
1
|
response['response']['docs'][0]['bibcode']
|
78
|
0
|
except IndexError:
|
79
|
0
|
raise RuntimeError('No results returned!')
|
80
|
|
|
81
|
|
# get the list of hits
|
82
|
1
|
hitlist = response['response']['docs']
|
83
|
|
|
84
|
1
|
t = Table()
|
85
|
|
# Grab the various fields and put into AstroPy table
|
86
|
1
|
for field in self.ADS_FIELDS:
|
87
|
1
|
tmp = _get_data_from_xml(hitlist, field)
|
88
|
1
|
t[field] = tmp
|
89
|
|
|
90
|
1
|
return t
|
91
|
|
|
92
|
1
|
def _args_to_url(self, query_string):
|
93
|
|
# convert arguments to a valid requests payload
|
94
|
|
# i.e. a dictionary
|
95
|
1
|
request_string = 'q=' + urlencode(query_string)
|
96
|
1
|
return request_string
|
97
|
|
|
98
|
1
|
def _fields_to_url(self):
|
99
|
1
|
request_fields = '&fl=' + ','.join(self.ADS_FIELDS)
|
100
|
1
|
return request_fields
|
101
|
|
|
102
|
1
|
def _sort_to_url(self):
|
103
|
1
|
request_sort = '&sort=' + urlencode(self.SORT)
|
104
|
1
|
return request_sort
|
105
|
|
|
106
|
1
|
def _rows_to_url(self, nrows=10, nstart=0):
|
107
|
1
|
request_rows = '&rows=' + str(nrows) + '&start=' + str(nstart)
|
108
|
1
|
return request_rows
|
109
|
|
|
110
|
1
|
def _get_token(self):
|
111
|
|
"""
|
112
|
|
Try to get token from the places Andy Casey's python ADS client expects it, otherwise return an error
|
113
|
|
"""
|
114
|
1
|
if self.TOKEN is not None:
|
115
|
1
|
return self.TOKEN
|
116
|
|
|
117
|
0
|
self.TOKEN = os.environ.get('ADS_DEV_KEY', None)
|
118
|
0
|
if self.TOKEN is not None:
|
119
|
0
|
return self.TOKEN
|
120
|
|
|
121
|
0
|
token_file = os.path.expanduser(os.path.join('~', '.ads', 'dev_key'))
|
122
|
0
|
try:
|
123
|
0
|
with open(token_file) as f:
|
124
|
0
|
self.TOKEN = f.read().strip()
|
125
|
0
|
return self.TOKEN
|
126
|
0
|
except IOError:
|
127
|
0
|
raise RuntimeError('No API token found! Get yours from: '
|
128
|
|
'https://ui.adsabs.harvard.edu/#user/settings/token '
|
129
|
|
'and store it in the API_DEV_KEY environment variable.')
|
130
|
|
|
131
|
|
|
132
|
1
|
ADS = ADSClass()
|