Coverage for tests/test_dynamic_connections.py: 12%

180 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-17 02:45 -0700

1# This file is part of pipe_base. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (http://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 software is dual licensed under the GNU General Public License and also 

10# under a 3-clause BSD license. Recipients may choose which of these licenses 

11# to use; please see the files gpl-3.0.txt and/or bsd_license.txt, 

12# respectively. If you choose the GPL option then the following text applies 

13# (but note that there is still no warranty even if you opt for BSD instead): 

14# 

15# This program is free software: you can redistribute it and/or modify 

16# it under the terms of the GNU General Public License as published by 

17# the Free Software Foundation, either version 3 of the License, or 

18# (at your option) any later version. 

19# 

20# This program is distributed in the hope that it will be useful, 

21# but WITHOUT ANY WARRANTY; without even the implied warranty of 

22# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

23# GNU General Public License for more details. 

24# 

25# You should have received a copy of the GNU General Public License 

26# along with this program. If not, see <http://www.gnu.org/licenses/>. 

27 

28from __future__ import annotations 

29 

30import unittest 

31from collections.abc import Callable 

32from types import MappingProxyType 

33 

34from lsst.pipe.base import PipelineTaskConfig, PipelineTaskConnections 

35from lsst.pipe.base.connectionTypes import Input, Output, PrerequisiteInput 

36 

37# Keyword arguments for defining our lone test connection in its class. 

38DEFAULT_CONNECTION_KWARGS = dict( 

39 doc="field docs", 

40 name="unconfigured", 

41 dimensions=(), 

42 storageClass="Dummy", 

43) 

44 

45# Keyword arguments for making our lone test connection post-configuration. 

46# We always rename the dataset type via configuration to make sure those 

47# changes are never dropped. 

48RENAMED_CONNECTION_KWARGS = DEFAULT_CONNECTION_KWARGS.copy() 

49RENAMED_CONNECTION_KWARGS["name"] = "configured" 

50 

51 

52class TestDynamicConnectionsClass(unittest.TestCase): 

53 """Test modifying connections in derived __init__ implementations.""" 

54 

55 def build_dynamic_connections( 

56 self, init_callback: Callable[[PipelineTaskConnections], None] = None 

57 ) -> PipelineTaskConnections: 

58 """Define and construct a connections class instance with a callback 

59 run in ``__init__``. 

60 

61 Parameters 

62 ---------- 

63 init_callback 

64 Callback to invoke with the `PipelineTaskConnections` instance 

65 as its only argument. Return value is ignored. 

66 

67 Returns 

68 ------- 

69 connections : `PipelineTaskConnections` 

70 Constructed connections instance. 

71 """ 

72 

73 class ExampleConnections(PipelineTaskConnections, dimensions=()): 

74 the_connection = Input(**DEFAULT_CONNECTION_KWARGS) 

75 

76 def __init__(self, config: ExampleConfig): 

77 # Calling super() is harmless but now unnecessary, so don't do 

78 # it to make sure that works. Old code calling it is fine. 

79 if init_callback is not None: 

80 init_callback(self) 

81 

82 class ExampleConfig(PipelineTaskConfig, pipelineConnections=ExampleConnections): 

83 pass 

84 

85 config = ExampleConfig() 

86 config.connections.the_connection = RENAMED_CONNECTION_KWARGS["name"] 

87 return ExampleConnections(config=config) 

88 

89 def test_freeze_after_construction(self): 

90 connections = self.build_dynamic_connections() 

91 self.assertIsInstance(connections.dimensions, frozenset) 

92 self.assertIsInstance(connections.inputs, frozenset) 

93 self.assertIsInstance(connections.prerequisiteInputs, frozenset) 

94 self.assertIsInstance(connections.outputs, frozenset) 

95 self.assertIsInstance(connections.initInputs, frozenset) 

96 self.assertIsInstance(connections.initOutputs, frozenset) 

97 self.assertIsInstance(connections.allConnections, MappingProxyType) 

98 

99 def test_change_attr_after_construction(self): 

100 connections = self.build_dynamic_connections() 

101 with self.assertRaises(TypeError): 

102 connections.the_connection = PrerequisiteInput(**RENAMED_CONNECTION_KWARGS) 

103 

104 def test_delete_attr_after_construction(self): 

105 connections = self.build_dynamic_connections() 

106 with self.assertRaises(TypeError): 

107 del connections.the_connection 

108 

109 def test_change_dimensions(self): 

110 def callback(instance): 

111 instance.dimensions = {"new", "dimensions"} 

112 instance.the_connection = Input( 

113 doc=instance.the_connection.doc, 

114 name=instance.the_connection.name, 

115 dimensions={"new", "dimensions"}, 

116 storageClass=instance.the_connection.storageClass, 

117 ) 

118 

119 connections = self.build_dynamic_connections(callback) 

120 self.assertEqual(connections.dimensions, {"new", "dimensions"}) 

121 self.assertIsInstance(connections.dimensions, frozenset) 

122 self.assertEqual(connections.inputs, {"the_connection"}) 

123 self.assertEqual(connections.allConnections.keys(), {"the_connection"}) 

124 updated_connection_kwargs = RENAMED_CONNECTION_KWARGS.copy() 

125 updated_connection_kwargs["dimensions"] = {"new", "dimensions"} 

126 self.assertEqual(connections.allConnections["the_connection"], Input(**updated_connection_kwargs)) 

127 self.assertEqual(connections.the_connection, Input(**updated_connection_kwargs)) 

128 

129 def test_change_connection_type(self): 

130 def callback(instance): 

131 instance.the_connection = PrerequisiteInput( 

132 doc=instance.the_connection.doc, 

133 name=instance.the_connection.name, 

134 dimensions=instance.the_connection.dimensions, 

135 storageClass=instance.the_connection.storageClass, 

136 ) 

137 

138 connections = self.build_dynamic_connections(callback) 

139 self.assertEqual(connections.inputs, set()) 

140 self.assertEqual(connections.prerequisiteInputs, {"the_connection"}) 

141 self.assertEqual(connections.allConnections.keys(), {"the_connection"}) 

142 self.assertEqual( 

143 connections.allConnections["the_connection"], PrerequisiteInput(**RENAMED_CONNECTION_KWARGS) 

144 ) 

145 self.assertEqual(connections.the_connection, PrerequisiteInput(**RENAMED_CONNECTION_KWARGS)) 

146 

147 def test_change_connection_type_twice(self): 

148 def callback(instance): 

149 instance.the_connection = PrerequisiteInput( 

150 doc=instance.the_connection.doc, 

151 name=instance.the_connection.name, 

152 dimensions=instance.the_connection.dimensions, 

153 storageClass=instance.the_connection.storageClass, 

154 ) 

155 instance.the_connection = Output( 

156 doc=instance.the_connection.doc, 

157 name=instance.the_connection.name, 

158 dimensions=instance.the_connection.dimensions, 

159 storageClass=instance.the_connection.storageClass, 

160 ) 

161 

162 connections = self.build_dynamic_connections(callback) 

163 self.assertEqual(connections.inputs, set()) 

164 self.assertEqual(connections.prerequisiteInputs, set()) 

165 self.assertEqual(connections.outputs, {"the_connection"}) 

166 self.assertEqual(connections.allConnections.keys(), {"the_connection"}) 

167 self.assertEqual(connections.allConnections["the_connection"], Output(**RENAMED_CONNECTION_KWARGS)) 

168 self.assertEqual(connections.the_connection, Output(**RENAMED_CONNECTION_KWARGS)) 

169 

170 def test_remove_from_set(self): 

171 def callback(instance): 

172 instance.inputs.remove("the_connection") 

173 # We can't make this remove corresponding attribute or the entry in 

174 # allConnections *immediately* without using a custom set class for 

175 # 'inputs' etc, which we haven't bothered to do, because even that 

176 # wouldn't be enough to have additions to those sets update the 

177 # attributes and allConnections. Instead updates to those happen 

178 # after __init__. 

179 

180 connections = self.build_dynamic_connections(callback) 

181 self.assertEqual(connections.inputs, set()) 

182 self.assertEqual(connections.allConnections, {}) 

183 with self.assertRaises(AttributeError): 

184 connections.the_connection 

185 

186 def test_delete_attr(self): 

187 def callback(instance): 

188 del instance.the_connection 

189 # This updates the corresponding entry from the inputs set and 

190 # the allConnections dict. 

191 self.assertEqual(instance.inputs, set()) 

192 self.assertEqual(instance.allConnections, {}) 

193 

194 connections = self.build_dynamic_connections(callback) 

195 self.assertEqual(connections.inputs, set()) 

196 self.assertEqual(connections.allConnections, {}) 

197 with self.assertRaises(AttributeError): 

198 connections.the_connection 

199 

200 def test_delete_attr_twice(self): 

201 def callback(instance): 

202 del instance.the_connection 

203 with self.assertRaises(AttributeError): 

204 del instance.the_connection 

205 

206 self.build_dynamic_connections(callback) 

207 

208 def test_change_connection_type_then_remove_from_set(self): 

209 def callback(instance): 

210 instance.the_connection = PrerequisiteInput( 

211 doc=instance.the_connection.doc, 

212 name=instance.the_connection.name, 

213 dimensions=instance.the_connection.dimensions, 

214 storageClass=instance.the_connection.storageClass, 

215 ) 

216 instance.prerequisiteInputs.remove("the_connection") 

217 

218 connections = self.build_dynamic_connections(callback) 

219 self.assertEqual(connections.inputs, set()) 

220 self.assertEqual(connections.prerequisiteInputs, set()) 

221 self.assertEqual(connections.allConnections, {}) 

222 with self.assertRaises(AttributeError): 

223 connections.the_connection 

224 with self.assertRaises(KeyError): 

225 connections.allConnections["the_connection"] 

226 

227 def test_change_connection_type_then_delete_attr(self): 

228 def callback(instance): 

229 instance.the_connection = PrerequisiteInput( 

230 doc=instance.the_connection.doc, 

231 name=instance.the_connection.name, 

232 dimensions=instance.the_connection.dimensions, 

233 storageClass=instance.the_connection.storageClass, 

234 ) 

235 del instance.the_connection 

236 # This updates the corresponding entry from the inputs set and 

237 # the allConnections dict. 

238 self.assertEqual(instance.inputs, set()) 

239 self.assertEqual(instance.prerequisiteInputs, set()) 

240 self.assertEqual(instance.allConnections, {}) 

241 with self.assertRaises(AttributeError): 

242 instance.the_connection 

243 with self.assertRaises(KeyError): 

244 instance.allConnections["the_connection"] 

245 

246 connections = self.build_dynamic_connections(callback) 

247 self.assertEqual(connections.inputs, set()) 

248 self.assertEqual(connections.prerequisiteInputs, set()) 

249 self.assertEqual(connections.allConnections, {}) 

250 with self.assertRaises(AttributeError): 

251 connections.the_connection 

252 with self.assertRaises(KeyError): 

253 connections.allConnections["the_connection"] 

254 

255 def test_add_new_connection(self): 

256 new_connection = Output( 

257 name="new_dataset_type", 

258 doc="new connection_docs", 

259 storageClass="Dummy", 

260 dimensions=(), 

261 ) 

262 

263 def callback(instance): 

264 instance.new_connection = new_connection 

265 self.assertEqual(instance.outputs, {"new_connection"}) 

266 self.assertEqual(instance.allConnections.keys(), {"new_connection", "the_connection"}) 

267 self.assertIs(instance.new_connection, new_connection) 

268 self.assertIs(instance.allConnections["new_connection"], new_connection) 

269 

270 connections = self.build_dynamic_connections(callback) 

271 self.assertEqual(connections.outputs, {"new_connection"}) 

272 self.assertEqual(connections.allConnections.keys(), {"new_connection", "the_connection"}) 

273 self.assertIs(connections.new_connection, new_connection) 

274 self.assertIs(connections.allConnections["new_connection"], new_connection) 

275 

276 def test_add_and_change_new_connection(self): 

277 new_connection = Output( 

278 name="new_dataset_type", 

279 doc="new connection_docs", 

280 storageClass="Dummy", 

281 dimensions=(), 

282 ) 

283 changed_connection = PrerequisiteInput( 

284 name="new_dataset_type", 

285 doc="new connection_docs", 

286 storageClass="Dummy", 

287 dimensions=(), 

288 ) 

289 

290 def callback(instance): 

291 instance.new_connection = new_connection 

292 self.assertEqual(instance.outputs, {"new_connection"}) 

293 self.assertEqual(instance.allConnections.keys(), {"new_connection", "the_connection"}) 

294 self.assertIs(instance.new_connection, new_connection) 

295 self.assertIs(instance.allConnections["new_connection"], new_connection) 

296 instance.new_connection = changed_connection 

297 self.assertEqual(instance.outputs, set()) 

298 self.assertEqual(instance.allConnections.keys(), {"new_connection", "the_connection"}) 

299 self.assertIs(instance.new_connection, changed_connection) 

300 self.assertIs(instance.allConnections["new_connection"], changed_connection) 

301 

302 connections = self.build_dynamic_connections(callback) 

303 self.assertEqual(connections.outputs, set()) 

304 self.assertEqual(connections.allConnections.keys(), {"new_connection", "the_connection"}) 

305 self.assertIs(connections.new_connection, changed_connection) 

306 self.assertIs(connections.allConnections["new_connection"], changed_connection) 

307 

308 def test_add_and_remove_new_connection(self): 

309 new_connection = Output( 

310 name="new_dataset_type", 

311 doc="new connection_docs", 

312 storageClass="Dummy", 

313 dimensions=(), 

314 ) 

315 

316 def callback(instance): 

317 instance.new_connection = new_connection 

318 self.assertEqual(instance.outputs, {"new_connection"}) 

319 self.assertEqual(instance.allConnections.keys(), {"new_connection", "the_connection"}) 

320 self.assertIs(instance.new_connection, new_connection) 

321 self.assertIs(instance.allConnections["new_connection"], new_connection) 

322 del instance.new_connection 

323 self.assertEqual(instance.outputs, set()) 

324 self.assertEqual(instance.allConnections.keys(), {"the_connection"}) 

325 with self.assertRaises(AttributeError): 

326 instance.new_connection 

327 with self.assertRaises(KeyError): 

328 instance.allConnections["new_connection"] 

329 

330 connections = self.build_dynamic_connections(callback) 

331 self.assertEqual(connections.outputs, set()) 

332 self.assertEqual(connections.allConnections.keys(), {"the_connection"}) 

333 with self.assertRaises(AttributeError): 

334 connections.new_connection 

335 with self.assertRaises(KeyError): 

336 connections.allConnections["new_connection"] 

337 

338 

339if __name__ == "__main__": 

340 unittest.main()