#
# LSST Data Management System
#
# This product includes software developed by the
# LSST Project (http://www.lsst.org/).
#
# See COPYRIGHT file at the top of the source tree.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the LSST License Statement and
# the GNU General Public License along with this program. If not,
# see <https://www.lsstcorp.org/LegalNotices/>.
#
"""SQUASH (https://squash.lsst.codes) client.
Data objects, particularly Job, Metric, and Specification, use this client to
upload and retrieve data from the SQUASH verification database.
SQUASH will likely be replaced by a database served behind Data Management's
webserv API. This client is considered a shim during construction.
"""
from __future__ import print_function
__all__ = ['get', 'post', 'get_endpoint_url', 'reset_endpoint_cache',
'get_default_timeout', 'get_default_api_version',
'make_accept_header']
import requests
import lsst.log
# Version of the SQUASH API this client is compatible with
_API_VERSION = '1.0'
# Default HTTP timeout (seconds) for all SQUASH client requests.
_TIMEOUT = 900.0
# URLs for SQUASH endpoints, cached by `get_endpoint_url()`.
_ENDPOINT_URLS = None
[docs]def get_endpoint_url(api_url, api_endpoint, **kwargs):
"""Lookup SQUASH endpoint URL.
Parameters
----------
api_url : `str`
Root URL of the SQUASH API. For example,
``'https://squash.lsst.codes/dashboard/api/'``.
api_endpoint : `str`
Name of the SQUASH API endpoint. For example, ``'job'``.
**kwargs : optional
Additional keyword arguments passed to `get`.
Returns
-------
endpoint_url : `str`
Full SQUASH endpoint URL.
Notes
-----
Endpoints are discovered from the SQUASH API itself. The SQUASH API is
queried on the first call to `get_endpoint_url`. Subsequent calls use
cached results for all endpoints. This cache can be reset with the
`reset_endpoint_cache` function.
"""
global _ENDPOINT_URLS
if _ENDPOINT_URLS is None:
r = get(api_url, **kwargs)
_ENDPOINT_URLS = r.json()
return _ENDPOINT_URLS[api_endpoint]
[docs]def reset_endpoint_cache():
"""Reset the cache used by `get_endpoint_url`.
"""
global _ENDPOINT_URLS
_ENDPOINT_URLS = None
[docs]def get_default_timeout():
"""Get the default HTTP client timeout setting.
Returns
-------
timeout : `float`
Default timeout setting, in seconds.
"""
global _TIMEOUT
return _TIMEOUT
[docs]def get_default_api_version():
"""Get the default SQUASH API versioned used by the lsst.verify.squash
client functions.
Returns
-------
version : `str`
API version. For example, ``'1.0'``.
"""
global _API_VERSION
return _API_VERSION
def get_access_token(api_url, api_user, api_password,
api_auth_endpoint='auth'):
"""Get access token from the SQUASH API assuming the API user exists.
Parameters
----------
api_url : `str`
Root URL of the SQUASH API. For example,
```https://squash-restful-api.lsst.codes```.
api_user : `str`
API username.
api_password : `str`
API password.
api_auth_endpoint : `str`
API authorization endpoint.
Returns
-------
access_token: `str`
The access token from the SQUASH API authorization endpoint.
"""
json_doc = {'username': api_user, 'password': api_password}
r = post(api_url, api_auth_endpoint, json_doc)
json_r = r.json()
return json_r['access_token']
def make_authorization_header(access_token):
"""Make an ``Authorization`` HTTP header using a SQUASH access token.
Parameters
----------
access_token : `str`
Access token returned by `get_access_token`.
Returns
-------
authorization_header : `str`
The Authorization header value.
"""
authorization_header = 'JWT {0}'
return authorization_header.format(access_token)
[docs]def post(api_url, api_endpoint, json_doc=None,
timeout=None, version=None, access_token=None):
"""POST a JSON document to SQUASH.
Parameters
----------
api_url : `str`
Root URL of the SQUASH API. For example,
``'https://squash.lsst.codes/api'``.
api_endpoint : `str`
Name of the API endpoint to post to.
json_doc : `dict`
A JSON-serializable object.
timeout : `float`, optional
Request timeout. The value of `get_default_timeout` is used by default.
version : `str`, optional
API version. The value of `get_default_api_version` is used by default.
access_token : `str`, optional
Access token (see `get_access_token`). Not required when a POST is done
to the API authorization endpoint.
Raises
------
requests.exceptions.RequestException
Raised if the HTTP request fails.
Returns
-------
response : `requests.Response`
Response object. Obtain JSON content with ``response.json()``.
"""
log = lsst.log.Log.getLogger('verify.squash.post')
api_endpoint_url = get_endpoint_url(api_url, api_endpoint)
headers = {
'Accept': make_accept_header(version)
}
if access_token:
headers['Authorization'] = make_authorization_header(access_token)
try:
# Disable redirect following for POST as requests will turn a POST into
# a GET when following a redirect. http://ls.st/pbx
r = requests.post(api_endpoint_url,
json=json_doc,
allow_redirects=False,
headers=headers,
timeout=timeout or get_default_timeout())
log.info('POST {0} status: {1}'.format(api_endpoint_url,
r.status_code))
r.raise_for_status()
# be pedantic about return status. requests#status_code will not error
# on 3xx codes
if r.status_code != 200 and r.status_code != 201 \
and r.status_code != 202:
message = 'Expected status = 200(OK), 201(Created) or 202' \
'(Accepted). Got status={0}. {1}'.format(r.status_code,
r.reason)
raise requests.exceptions.RequestException(message)
except requests.exceptions.RequestException as e:
log.error(str(e))
raise e
return r
[docs]def get(api_url, api_endpoint=None,
api_user=None, api_password=None, timeout=None, version=None):
"""GET request to the SQUASH API.
Parameters
----------
api_url : `str`
Root URL of the SQUASH API. For example,
``'https://squash.lsst.codes/api'``.
api_endpoint : `str`, optional
Name of the API endpoint to post to. The ``api_url`` is requested if
unset.
api_user : `str`, optional
API username.
api_password : `str`, optional
API password.
timeout : `float`, optional
Request timeout. The value of `get_default_timeout` is used by default.
version : `str`, optional
API version. The value of `get_default_api_version` is used by default.
Raises
------
requests.exceptions.RequestException
Raised if the HTTP request fails.
Returns
-------
response : `requests.Response`
Response object. Obtain JSON content with ``response.json()``.
"""
log = lsst.log.Log.getLogger('verify.squash.get')
if api_user is not None and api_password is not None:
auth = (api_user, api_password)
else:
auth = None
if api_endpoint is not None:
api_endpoint_url = get_endpoint_url(api_url, api_endpoint)
else:
api_endpoint_url = api_url
headers = {
'Accept': make_accept_header(version)
}
try:
r = requests.get(api_endpoint_url,
auth=auth,
headers=headers,
timeout=timeout or get_default_timeout())
log.info('GET {0} status: {1}'.format(api_endpoint_url,
r.status_code))
r.raise_for_status()
except requests.exceptions.RequestException as e:
log.error(str(e))
raise e
return r