Coverage for python/lsst/verify/squash.py: 22%

66 statements  

« prev     ^ index     » next       coverage.py v7.2.5, created at 2023-05-13 02:19 -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. 

22 

23Data objects, particularly Job, Metric, and Specification, use this client to 

24upload and retrieve data from the SQUASH verification database. 

25 

26SQUASH will likely be replaced by a database served behind Data Management's 

27webserv API. This client is considered a shim during construction. 

28""" 

29 

30__all__ = ['get', 'post', 'get_endpoint_url', 'reset_endpoint_cache', 

31 'get_default_timeout', 'get_default_api_version', 

32 'make_accept_header'] 

33 

34 

35import requests 

36import logging 

37 

38# Version of the SQUASH API this client is compatible with 

39_API_VERSION = '1.0' 

40 

41# Default HTTP timeout (seconds) for all SQUASH client requests. 

42_TIMEOUT = 900.0 

43 

44# URLs for SQUASH endpoints, cached by `get_endpoint_url()`. 

45_ENDPOINT_URLS = None 

46 

47_LOG = logging.getLogger(__name__) 

48 

49 

50def get_endpoint_url(api_url, api_endpoint, **kwargs): 

51 """Lookup SQUASH endpoint URL. 

52 

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`. 

62 

63 Returns 

64 ------- 

65 endpoint_url : `str` 

66 Full SQUASH endpoint URL. 

67 

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 

76 

77 if _ENDPOINT_URLS is None: 

78 r = get(api_url, **kwargs) 

79 _ENDPOINT_URLS = r.json() 

80 

81 return _ENDPOINT_URLS[api_endpoint] 

82 

83 

84def reset_endpoint_cache(): 

85 """Reset the cache used by `get_endpoint_url`. 

86 """ 

87 global _ENDPOINT_URLS 

88 _ENDPOINT_URLS = None 

89 

90 

91def get_default_timeout(): 

92 """Get the default HTTP client timeout setting. 

93 

94 Returns 

95 ------- 

96 timeout : `float` 

97 Default timeout setting, in seconds. 

98 """ 

99 global _TIMEOUT 

100 return _TIMEOUT 

101 

102 

103def get_default_api_version(): 

104 """Get the default SQUASH API versioned used by the lsst.verify.squash 

105 client functions. 

106 

107 Returns 

108 ------- 

109 version : `str` 

110 API version. For example, ``'1.0'``. 

111 """ 

112 global _API_VERSION 

113 return _API_VERSION 

114 

115 

116def make_accept_header(version=None): 

117 """Make the ``Accept`` HTTP header for versioned SQUASH API requests. 

118 

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`). 

124 

125 Returns 

126 ------- 

127 accept_header : `str` 

128 The ``Accept`` header value. 

129 

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) 

139 

140 

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. 

144 

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. 

156 

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} 

163 

164 r = post(api_url, api_auth_endpoint, json_doc) 

165 

166 json_r = r.json() 

167 

168 return json_r['access_token'] 

169 

170 

171def make_authorization_header(access_token): 

172 """Make an ``Authorization`` HTTP header using a SQUASH access token. 

173 

174 Parameters 

175 ---------- 

176 access_token : `str` 

177 Access token returned by `get_access_token`. 

178 

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) 

186 

187 

188def post(api_url, api_endpoint, json_doc=None, 

189 timeout=None, version=None, access_token=None): 

190 """POST a JSON document to SQUASH. 

191 

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. 

208 

209 Raises 

210 ------ 

211 requests.exceptions.RequestException 

212 Raised if the HTTP request fails. 

213 

214 Returns 

215 ------- 

216 response : `requests.Response` 

217 Response object. Obtain JSON content with ``response.json()``. 

218 """ 

219 log = _LOG.getChild('post') 

220 

221 api_endpoint_url = get_endpoint_url(api_url, api_endpoint) 

222 

223 headers = { 

224 'Accept': make_accept_header(version) 

225 } 

226 

227 if access_token: 

228 headers['Authorization'] = make_authorization_header(access_token) 

229 

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() 

241 

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 

253 

254 return r 

255 

256 

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. 

260 

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. 

277 

278 Raises 

279 ------ 

280 requests.exceptions.RequestException 

281 Raised if the HTTP request fails. 

282 

283 Returns 

284 ------- 

285 response : `requests.Response` 

286 Response object. Obtain JSON content with ``response.json()``. 

287 """ 

288 log = _LOG.getChild('get') 

289 

290 if api_user is not None and api_password is not None: 

291 auth = (api_user, api_password) 

292 else: 

293 auth = None 

294 

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 

299 

300 headers = { 

301 'Accept': make_accept_header(version) 

302 } 

303 

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 

315 

316 return r