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

65 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-23 08:29 +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 

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 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 return _API_VERSION 

112 

113 

114def make_accept_header(version=None): 

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

116 

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

122 

123 Returns 

124 ------- 

125 accept_header : `str` 

126 The ``Accept`` header value. 

127 

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) 

137 

138 

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. 

142 

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. 

154 

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} 

161 

162 r = post(api_url, api_auth_endpoint, json_doc) 

163 

164 json_r = r.json() 

165 

166 return json_r['access_token'] 

167 

168 

169def make_authorization_header(access_token): 

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

171 

172 Parameters 

173 ---------- 

174 access_token : `str` 

175 Access token returned by `get_access_token`. 

176 

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) 

184 

185 

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

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

188 """POST a JSON document to SQUASH. 

189 

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. 

206 

207 Raises 

208 ------ 

209 requests.exceptions.RequestException 

210 Raised if the HTTP request fails. 

211 

212 Returns 

213 ------- 

214 response : `requests.Response` 

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

216 """ 

217 log = _LOG.getChild('post') 

218 

219 api_endpoint_url = get_endpoint_url(api_url, api_endpoint) 

220 

221 headers = { 

222 'Accept': make_accept_header(version) 

223 } 

224 

225 if access_token: 

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

227 

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

239 

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 

251 

252 return r 

253 

254 

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. 

258 

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. 

275 

276 Raises 

277 ------ 

278 requests.exceptions.RequestException 

279 Raised if the HTTP request fails. 

280 

281 Returns 

282 ------- 

283 response : `requests.Response` 

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

285 """ 

286 log = _LOG.getChild('get') 

287 

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

289 auth = (api_user, api_password) 

290 else: 

291 auth = None 

292 

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 

297 

298 headers = { 

299 'Accept': make_accept_header(version) 

300 } 

301 

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 

313 

314 return r