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

65 statements  

« prev     ^ index     » next       coverage.py v6.4.1, created at 2022-07-11 06:50 +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. 

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 

36 

37import lsst.log 

38 

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

40_API_VERSION = '1.0' 

41 

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

43_TIMEOUT = 900.0 

44 

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

46_ENDPOINT_URLS = None 

47 

48 

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

50 """Lookup SQUASH endpoint URL. 

51 

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

61 

62 Returns 

63 ------- 

64 endpoint_url : `str` 

65 Full SQUASH endpoint URL. 

66 

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 

75 

76 if _ENDPOINT_URLS is None: 

77 r = get(api_url, **kwargs) 

78 _ENDPOINT_URLS = r.json() 

79 

80 return _ENDPOINT_URLS[api_endpoint] 

81 

82 

83def reset_endpoint_cache(): 

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

85 """ 

86 global _ENDPOINT_URLS 

87 _ENDPOINT_URLS = None 

88 

89 

90def get_default_timeout(): 

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

92 

93 Returns 

94 ------- 

95 timeout : `float` 

96 Default timeout setting, in seconds. 

97 """ 

98 global _TIMEOUT 

99 return _TIMEOUT 

100 

101 

102def get_default_api_version(): 

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

104 client functions. 

105 

106 Returns 

107 ------- 

108 version : `str` 

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

110 """ 

111 global _API_VERSION 

112 return _API_VERSION 

113 

114 

115def make_accept_header(version=None): 

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

117 

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

123 

124 Returns 

125 ------- 

126 accept_header : `str` 

127 The ``Accept`` header value. 

128 

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) 

138 

139 

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. 

143 

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. 

155 

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} 

162 

163 r = post(api_url, api_auth_endpoint, json_doc) 

164 

165 json_r = r.json() 

166 

167 return json_r['access_token'] 

168 

169 

170def make_authorization_header(access_token): 

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

172 

173 Parameters 

174 ---------- 

175 access_token : `str` 

176 Access token returned by `get_access_token`. 

177 

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) 

185 

186 

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

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

189 """POST a JSON document to SQUASH. 

190 

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. 

207 

208 Raises 

209 ------ 

210 requests.exceptions.RequestException 

211 Raised if the HTTP request fails. 

212 

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

219 

220 api_endpoint_url = get_endpoint_url(api_url, api_endpoint) 

221 

222 headers = { 

223 'Accept': make_accept_header(version) 

224 } 

225 

226 if access_token: 

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

228 

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

240 

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 

252 

253 return r 

254 

255 

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. 

259 

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. 

276 

277 Raises 

278 ------ 

279 requests.exceptions.RequestException 

280 Raised if the HTTP request fails. 

281 

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

288 

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

290 auth = (api_user, api_password) 

291 else: 

292 auth = None 

293 

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 

298 

299 headers = { 

300 'Accept': make_accept_header(version) 

301 } 

302 

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 

314 

315 return r