Coverage for python / lsst / verify / squash.py: 21%
65 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 08:53 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-30 08:53 +0000
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 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 return _API_VERSION
114def make_accept_header(version=None):
115 """Make the ``Accept`` HTTP header for versioned SQUASH API requests.
117 Parameters
118 ----------
119 version : `str`, optional
120 Semantic API version, such as ``'1.0'``. By default, the API version
121 this client is designed for is used (`get_default_api_version`).
123 Returns
124 -------
125 accept_header : `str`
126 The ``Accept`` header value.
128 Examples
129 --------
130 >>> make_accept_header()
131 'application/json; version=1.0'
132 """
133 if version is None:
134 version = get_default_api_version()
135 template = 'application/json; version={0}'
136 return template.format(version)
139def get_access_token(api_url, api_user, api_password,
140 api_auth_endpoint='auth'):
141 """Get access token from the SQUASH API assuming the API user exists.
143 Parameters
144 ----------
145 api_url : `str`
146 Root URL of the SQUASH API. For example,
147 ```https://squash-restful-api.lsst.codes```.
148 api_user : `str`
149 API username.
150 api_password : `str`
151 API password.
152 api_auth_endpoint : `str`
153 API authorization endpoint.
155 Returns
156 -------
157 access_token: `str`
158 The access token from the SQUASH API authorization endpoint.
159 """
160 json_doc = {'username': api_user, 'password': api_password}
162 r = post(api_url, api_auth_endpoint, json_doc)
164 json_r = r.json()
166 return json_r['access_token']
169def make_authorization_header(access_token):
170 """Make an ``Authorization`` HTTP header using a SQUASH access token.
172 Parameters
173 ----------
174 access_token : `str`
175 Access token returned by `get_access_token`.
177 Returns
178 -------
179 authorization_header : `str`
180 The Authorization header value.
181 """
182 authorization_header = 'JWT {0}'
183 return authorization_header.format(access_token)
186def post(api_url, api_endpoint, json_doc=None,
187 timeout=None, version=None, access_token=None):
188 """POST a JSON document to SQUASH.
190 Parameters
191 ----------
192 api_url : `str`
193 Root URL of the SQUASH API. For example,
194 ``'https://squash.lsst.codes/api'``.
195 api_endpoint : `str`
196 Name of the API endpoint to post to.
197 json_doc : `dict`
198 A JSON-serializable object.
199 timeout : `float`, optional
200 Request timeout. The value of `get_default_timeout` is used by default.
201 version : `str`, optional
202 API version. The value of `get_default_api_version` is used by default.
203 access_token : `str`, optional
204 Access token (see `get_access_token`). Not required when a POST is done
205 to the API authorization endpoint.
207 Raises
208 ------
209 requests.exceptions.RequestException
210 Raised if the HTTP request fails.
212 Returns
213 -------
214 response : `requests.Response`
215 Response object. Obtain JSON content with ``response.json()``.
216 """
217 log = _LOG.getChild('post')
219 api_endpoint_url = get_endpoint_url(api_url, api_endpoint)
221 headers = {
222 'Accept': make_accept_header(version)
223 }
225 if access_token:
226 headers['Authorization'] = make_authorization_header(access_token)
228 try:
229 # Disable redirect following for POST as requests will turn a POST into
230 # a GET when following a redirect. http://ls.st/pbx
231 r = requests.post(api_endpoint_url,
232 json=json_doc,
233 allow_redirects=False,
234 headers=headers,
235 timeout=timeout or get_default_timeout())
236 log.info('POST {0} status: {1}'.format(api_endpoint_url,
237 r.status_code))
238 r.raise_for_status()
240 # be pedantic about return status. requests#status_code will not error
241 # on 3xx codes
242 if r.status_code != 200 and r.status_code != 201 \
243 and r.status_code != 202:
244 message = 'Expected status = 200(OK), 201(Created) or 202' \
245 '(Accepted). Got status={0}. {1}'.format(r.status_code,
246 r.reason)
247 raise requests.exceptions.RequestException(message)
248 except requests.exceptions.RequestException as e:
249 log.error(str(e))
250 raise e
252 return r
255def get(api_url, api_endpoint=None,
256 api_user=None, api_password=None, timeout=None, version=None):
257 """GET request to the SQUASH API.
259 Parameters
260 ----------
261 api_url : `str`
262 Root URL of the SQUASH API. For example,
263 ``'https://squash.lsst.codes/api'``.
264 api_endpoint : `str`, optional
265 Name of the API endpoint to post to. The ``api_url`` is requested if
266 unset.
267 api_user : `str`, optional
268 API username.
269 api_password : `str`, optional
270 API password.
271 timeout : `float`, optional
272 Request timeout. The value of `get_default_timeout` is used by default.
273 version : `str`, optional
274 API version. The value of `get_default_api_version` is used by default.
276 Raises
277 ------
278 requests.exceptions.RequestException
279 Raised if the HTTP request fails.
281 Returns
282 -------
283 response : `requests.Response`
284 Response object. Obtain JSON content with ``response.json()``.
285 """
286 log = _LOG.getChild('get')
288 if api_user is not None and api_password is not None:
289 auth = (api_user, api_password)
290 else:
291 auth = None
293 if api_endpoint is not None:
294 api_endpoint_url = get_endpoint_url(api_url, api_endpoint)
295 else:
296 api_endpoint_url = api_url
298 headers = {
299 'Accept': make_accept_header(version)
300 }
302 try:
303 r = requests.get(api_endpoint_url,
304 auth=auth,
305 headers=headers,
306 timeout=timeout or get_default_timeout())
307 log.info('GET {0} status: {1}'.format(api_endpoint_url,
308 r.status_code))
309 r.raise_for_status()
310 except requests.exceptions.RequestException as e:
311 log.error(str(e))
312 raise e
314 return r