Coverage for python/lsst/verify/squash.py: 21%
65 statements
« prev ^ index » next coverage.py v7.2.1, created at 2023-03-12 01:54 -0800
« prev ^ index » next coverage.py v7.2.1, created at 2023-03-12 01:54 -0800
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
37import lsst.log
39# Version of the SQUASH API this client is compatible with
40_API_VERSION = '1.0'
42# Default HTTP timeout (seconds) for all SQUASH client requests.
43_TIMEOUT = 900.0
45# URLs for SQUASH endpoints, cached by `get_endpoint_url()`.
46_ENDPOINT_URLS = None
49def get_endpoint_url(api_url, api_endpoint, **kwargs):
50 """Lookup SQUASH endpoint URL.
52 Parameters
53 ----------
54 api_url : `str`
55 Root URL of the SQUASH API. For example,
56 ``'https://squash.lsst.codes/dashboard/api/'``.
57 api_endpoint : `str`
58 Name of the SQUASH API endpoint. For example, ``'job'``.
59 **kwargs : optional
60 Additional keyword arguments passed to `get`.
62 Returns
63 -------
64 endpoint_url : `str`
65 Full SQUASH endpoint URL.
67 Notes
68 -----
69 Endpoints are discovered from the SQUASH API itself. The SQUASH API is
70 queried on the first call to `get_endpoint_url`. Subsequent calls use
71 cached results for all endpoints. This cache can be reset with the
72 `reset_endpoint_cache` function.
73 """
74 global _ENDPOINT_URLS
76 if _ENDPOINT_URLS is None:
77 r = get(api_url, **kwargs)
78 _ENDPOINT_URLS = r.json()
80 return _ENDPOINT_URLS[api_endpoint]
83def reset_endpoint_cache():
84 """Reset the cache used by `get_endpoint_url`.
85 """
86 global _ENDPOINT_URLS
87 _ENDPOINT_URLS = None
90def get_default_timeout():
91 """Get the default HTTP client timeout setting.
93 Returns
94 -------
95 timeout : `float`
96 Default timeout setting, in seconds.
97 """
98 global _TIMEOUT
99 return _TIMEOUT
102def get_default_api_version():
103 """Get the default SQUASH API versioned used by the lsst.verify.squash
104 client functions.
106 Returns
107 -------
108 version : `str`
109 API version. For example, ``'1.0'``.
110 """
111 global _API_VERSION
112 return _API_VERSION
115def make_accept_header(version=None):
116 """Make the ``Accept`` HTTP header for versioned SQUASH API requests.
118 Parameters
119 ----------
120 version : `str`, optional
121 Semantic API version, such as ``'1.0'``. By default, the API version
122 this client is designed for is used (`get_default_api_version`).
124 Returns
125 -------
126 accept_header : `str`
127 The ``Accept`` header value.
129 Examples
130 --------
131 >>> make_accept_header()
132 'application/json; version=1.0'
133 """
134 if version is None:
135 version = get_default_api_version()
136 template = 'application/json; version={0}'
137 return template.format(version)
140def get_access_token(api_url, api_user, api_password,
141 api_auth_endpoint='auth'):
142 """Get access token from the SQUASH API assuming the API user exists.
144 Parameters
145 ----------
146 api_url : `str`
147 Root URL of the SQUASH API. For example,
148 ```https://squash-restful-api.lsst.codes```.
149 api_user : `str`
150 API username.
151 api_password : `str`
152 API password.
153 api_auth_endpoint : `str`
154 API authorization endpoint.
156 Returns
157 -------
158 access_token: `str`
159 The access token from the SQUASH API authorization endpoint.
160 """
161 json_doc = {'username': api_user, 'password': api_password}
163 r = post(api_url, api_auth_endpoint, json_doc)
165 json_r = r.json()
167 return json_r['access_token']
170def make_authorization_header(access_token):
171 """Make an ``Authorization`` HTTP header using a SQUASH access token.
173 Parameters
174 ----------
175 access_token : `str`
176 Access token returned by `get_access_token`.
178 Returns
179 -------
180 authorization_header : `str`
181 The Authorization header value.
182 """
183 authorization_header = 'JWT {0}'
184 return authorization_header.format(access_token)
187def post(api_url, api_endpoint, json_doc=None,
188 timeout=None, version=None, access_token=None):
189 """POST a JSON document to SQUASH.
191 Parameters
192 ----------
193 api_url : `str`
194 Root URL of the SQUASH API. For example,
195 ``'https://squash.lsst.codes/api'``.
196 api_endpoint : `str`
197 Name of the API endpoint to post to.
198 json_doc : `dict`
199 A JSON-serializable object.
200 timeout : `float`, optional
201 Request timeout. The value of `get_default_timeout` is used by default.
202 version : `str`, optional
203 API version. The value of `get_default_api_version` is used by default.
204 access_token : `str`, optional
205 Access token (see `get_access_token`). Not required when a POST is done
206 to the API authorization endpoint.
208 Raises
209 ------
210 requests.exceptions.RequestException
211 Raised if the HTTP request fails.
213 Returns
214 -------
215 response : `requests.Response`
216 Response object. Obtain JSON content with ``response.json()``.
217 """
218 log = lsst.log.Log.getLogger('verify.squash.post')
220 api_endpoint_url = get_endpoint_url(api_url, api_endpoint)
222 headers = {
223 'Accept': make_accept_header(version)
224 }
226 if access_token:
227 headers['Authorization'] = make_authorization_header(access_token)
229 try:
230 # Disable redirect following for POST as requests will turn a POST into
231 # a GET when following a redirect. http://ls.st/pbx
232 r = requests.post(api_endpoint_url,
233 json=json_doc,
234 allow_redirects=False,
235 headers=headers,
236 timeout=timeout or get_default_timeout())
237 log.info('POST {0} status: {1}'.format(api_endpoint_url,
238 r.status_code))
239 r.raise_for_status()
241 # be pedantic about return status. requests#status_code will not error
242 # on 3xx codes
243 if r.status_code != 200 and r.status_code != 201 \
244 and r.status_code != 202:
245 message = 'Expected status = 200(OK), 201(Created) or 202' \
246 '(Accepted). Got status={0}. {1}'.format(r.status_code,
247 r.reason)
248 raise requests.exceptions.RequestException(message)
249 except requests.exceptions.RequestException as e:
250 log.error(str(e))
251 raise e
253 return r
256def get(api_url, api_endpoint=None,
257 api_user=None, api_password=None, timeout=None, version=None):
258 """GET request to the SQUASH API.
260 Parameters
261 ----------
262 api_url : `str`
263 Root URL of the SQUASH API. For example,
264 ``'https://squash.lsst.codes/api'``.
265 api_endpoint : `str`, optional
266 Name of the API endpoint to post to. The ``api_url`` is requested if
267 unset.
268 api_user : `str`, optional
269 API username.
270 api_password : `str`, optional
271 API password.
272 timeout : `float`, optional
273 Request timeout. The value of `get_default_timeout` is used by default.
274 version : `str`, optional
275 API version. The value of `get_default_api_version` is used by default.
277 Raises
278 ------
279 requests.exceptions.RequestException
280 Raised if the HTTP request fails.
282 Returns
283 -------
284 response : `requests.Response`
285 Response object. Obtain JSON content with ``response.json()``.
286 """
287 log = lsst.log.Log.getLogger('verify.squash.get')
289 if api_user is not None and api_password is not None:
290 auth = (api_user, api_password)
291 else:
292 auth = None
294 if api_endpoint is not None:
295 api_endpoint_url = get_endpoint_url(api_url, api_endpoint)
296 else:
297 api_endpoint_url = api_url
299 headers = {
300 'Accept': make_accept_header(version)
301 }
303 try:
304 r = requests.get(api_endpoint_url,
305 auth=auth,
306 headers=headers,
307 timeout=timeout or get_default_timeout())
308 log.info('GET {0} status: {1}'.format(api_endpoint_url,
309 r.status_code))
310 r.raise_for_status()
311 except requests.exceptions.RequestException as e:
312 log.error(str(e))
313 raise e
315 return r