Coverage for python/lsst/verify/squash.py: 22%
66 statements
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-11 02:59 -0700
« prev ^ index » next coverage.py v6.4.1, created at 2022-06-11 02:59 -0700
1# This file is part of verify.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
21"""SQUASH (https://squash.lsst.codes) client.
23Data objects, particularly Job, Metric, and Specification, use this client to
24upload and retrieve data from the SQUASH verification database.
26SQUASH will likely be replaced by a database served behind Data Management's
27webserv API. This client is considered a shim during construction.
28"""
30__all__ = ['get', 'post', 'get_endpoint_url', 'reset_endpoint_cache',
31 'get_default_timeout', 'get_default_api_version',
32 'make_accept_header']
35import requests
36import logging
38# Version of the SQUASH API this client is compatible with
39_API_VERSION = '1.0'
41# Default HTTP timeout (seconds) for all SQUASH client requests.
42_TIMEOUT = 900.0
44# URLs for SQUASH endpoints, cached by `get_endpoint_url()`.
45_ENDPOINT_URLS = None
47_LOG = logging.getLogger(__name__)
50def get_endpoint_url(api_url, api_endpoint, **kwargs):
51 """Lookup SQUASH endpoint URL.
53 Parameters
54 ----------
55 api_url : `str`
56 Root URL of the SQUASH API. For example,
57 ``'https://squash.lsst.codes/dashboard/api/'``.
58 api_endpoint : `str`
59 Name of the SQUASH API endpoint. For example, ``'job'``.
60 **kwargs : optional
61 Additional keyword arguments passed to `get`.
63 Returns
64 -------
65 endpoint_url : `str`
66 Full SQUASH endpoint URL.
68 Notes
69 -----
70 Endpoints are discovered from the SQUASH API itself. The SQUASH API is
71 queried on the first call to `get_endpoint_url`. Subsequent calls use
72 cached results for all endpoints. This cache can be reset with the
73 `reset_endpoint_cache` function.
74 """
75 global _ENDPOINT_URLS
77 if _ENDPOINT_URLS is None:
78 r = get(api_url, **kwargs)
79 _ENDPOINT_URLS = r.json()
81 return _ENDPOINT_URLS[api_endpoint]
84def reset_endpoint_cache():
85 """Reset the cache used by `get_endpoint_url`.
86 """
87 global _ENDPOINT_URLS
88 _ENDPOINT_URLS = None
91def get_default_timeout():
92 """Get the default HTTP client timeout setting.
94 Returns
95 -------
96 timeout : `float`
97 Default timeout setting, in seconds.
98 """
99 global _TIMEOUT
100 return _TIMEOUT
103def get_default_api_version():
104 """Get the default SQUASH API versioned used by the lsst.verify.squash
105 client functions.
107 Returns
108 -------
109 version : `str`
110 API version. For example, ``'1.0'``.
111 """
112 global _API_VERSION
113 return _API_VERSION
116def make_accept_header(version=None):
117 """Make the ``Accept`` HTTP header for versioned SQUASH API requests.
119 Parameters
120 ----------
121 version : `str`, optional
122 Semantic API version, such as ``'1.0'``. By default, the API version
123 this client is designed for is used (`get_default_api_version`).
125 Returns
126 -------
127 accept_header : `str`
128 The ``Accept`` header value.
130 Examples
131 --------
132 >>> make_accept_header()
133 'application/json; version=1.0'
134 """
135 if version is None:
136 version = get_default_api_version()
137 template = 'application/json; version={0}'
138 return template.format(version)
141def get_access_token(api_url, api_user, api_password,
142 api_auth_endpoint='auth'):
143 """Get access token from the SQUASH API assuming the API user exists.
145 Parameters
146 ----------
147 api_url : `str`
148 Root URL of the SQUASH API. For example,
149 ```https://squash-restful-api.lsst.codes```.
150 api_user : `str`
151 API username.
152 api_password : `str`
153 API password.
154 api_auth_endpoint : `str`
155 API authorization endpoint.
157 Returns
158 -------
159 access_token: `str`
160 The access token from the SQUASH API authorization endpoint.
161 """
162 json_doc = {'username': api_user, 'password': api_password}
164 r = post(api_url, api_auth_endpoint, json_doc)
166 json_r = r.json()
168 return json_r['access_token']
171def make_authorization_header(access_token):
172 """Make an ``Authorization`` HTTP header using a SQUASH access token.
174 Parameters
175 ----------
176 access_token : `str`
177 Access token returned by `get_access_token`.
179 Returns
180 -------
181 authorization_header : `str`
182 The Authorization header value.
183 """
184 authorization_header = 'JWT {0}'
185 return authorization_header.format(access_token)
188def post(api_url, api_endpoint, json_doc=None,
189 timeout=None, version=None, access_token=None):
190 """POST a JSON document to SQUASH.
192 Parameters
193 ----------
194 api_url : `str`
195 Root URL of the SQUASH API. For example,
196 ``'https://squash.lsst.codes/api'``.
197 api_endpoint : `str`
198 Name of the API endpoint to post to.
199 json_doc : `dict`
200 A JSON-serializable object.
201 timeout : `float`, optional
202 Request timeout. The value of `get_default_timeout` is used by default.
203 version : `str`, optional
204 API version. The value of `get_default_api_version` is used by default.
205 access_token : `str`, optional
206 Access token (see `get_access_token`). Not required when a POST is done
207 to the API authorization endpoint.
209 Raises
210 ------
211 requests.exceptions.RequestException
212 Raised if the HTTP request fails.
214 Returns
215 -------
216 response : `requests.Response`
217 Response object. Obtain JSON content with ``response.json()``.
218 """
219 log = _LOG.getChild('post')
221 api_endpoint_url = get_endpoint_url(api_url, api_endpoint)
223 headers = {
224 'Accept': make_accept_header(version)
225 }
227 if access_token:
228 headers['Authorization'] = make_authorization_header(access_token)
230 try:
231 # Disable redirect following for POST as requests will turn a POST into
232 # a GET when following a redirect. http://ls.st/pbx
233 r = requests.post(api_endpoint_url,
234 json=json_doc,
235 allow_redirects=False,
236 headers=headers,
237 timeout=timeout or get_default_timeout())
238 log.info('POST {0} status: {1}'.format(api_endpoint_url,
239 r.status_code))
240 r.raise_for_status()
242 # be pedantic about return status. requests#status_code will not error
243 # on 3xx codes
244 if r.status_code != 200 and r.status_code != 201 \
245 and r.status_code != 202:
246 message = 'Expected status = 200(OK), 201(Created) or 202' \
247 '(Accepted). Got status={0}. {1}'.format(r.status_code,
248 r.reason)
249 raise requests.exceptions.RequestException(message)
250 except requests.exceptions.RequestException as e:
251 log.error(str(e))
252 raise e
254 return r
257def get(api_url, api_endpoint=None,
258 api_user=None, api_password=None, timeout=None, version=None):
259 """GET request to the SQUASH API.
261 Parameters
262 ----------
263 api_url : `str`
264 Root URL of the SQUASH API. For example,
265 ``'https://squash.lsst.codes/api'``.
266 api_endpoint : `str`, optional
267 Name of the API endpoint to post to. The ``api_url`` is requested if
268 unset.
269 api_user : `str`, optional
270 API username.
271 api_password : `str`, optional
272 API password.
273 timeout : `float`, optional
274 Request timeout. The value of `get_default_timeout` is used by default.
275 version : `str`, optional
276 API version. The value of `get_default_api_version` is used by default.
278 Raises
279 ------
280 requests.exceptions.RequestException
281 Raised if the HTTP request fails.
283 Returns
284 -------
285 response : `requests.Response`
286 Response object. Obtain JSON content with ``response.json()``.
287 """
288 log = _LOG.getChild('get')
290 if api_user is not None and api_password is not None:
291 auth = (api_user, api_password)
292 else:
293 auth = None
295 if api_endpoint is not None:
296 api_endpoint_url = get_endpoint_url(api_url, api_endpoint)
297 else:
298 api_endpoint_url = api_url
300 headers = {
301 'Accept': make_accept_header(version)
302 }
304 try:
305 r = requests.get(api_endpoint_url,
306 auth=auth,
307 headers=headers,
308 timeout=timeout or get_default_timeout())
309 log.info('GET {0} status: {1}'.format(api_endpoint_url,
310 r.status_code))
311 r.raise_for_status()
312 except requests.exceptions.RequestException as e:
313 log.error(str(e))
314 raise e
316 return r