Coverage for python / lsst / pipe / base / connectionTypes.py: 75%

63 statements  

« prev     ^ index     » next       coverage.py v7.12.0, created at 2025-12-12 01:14 -0800

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 

28"""Module defining connection types to be used within a 

29`.PipelineTaskConnections` class. 

30""" 

31 

32__all__ = ["BaseConnection", "InitInput", "InitOutput", "Input", "Output", "PrerequisiteInput"] 

33 

34import dataclasses 

35from collections.abc import Callable, Iterable, Sequence 

36from typing import ClassVar 

37 

38from lsst.daf.butler import DataCoordinate, DatasetRef, DatasetType, Registry 

39from lsst.utils.introspection import find_outside_stacklevel 

40 

41 

42@dataclasses.dataclass(frozen=True) 

43class BaseConnection: 

44 """Base class used for declaring `PipelineTask` connections. 

45 

46 Attributes 

47 ---------- 

48 name : `str` 

49 The name used to identify the dataset type. 

50 storageClass : `str` 

51 The storage class used when (un)/persisting the dataset type. 

52 multiple : `bool` 

53 Indicates if this connection should expect to contain multiple objects 

54 of the given dataset type. Tasks with more than one connection with 

55 ``multiple=True`` with the same dimensions may want to implement 

56 `.PipelineTaskConnections.adjustQuantum` to ensure those datasets are 

57 consistent (i.e. zip-iterable) in `PipelineTask.runQuantum()` and 

58 notify the execution system as early as possible of outputs that will 

59 not be produced because the corresponding input is missing. 

60 deprecated : `str`, optional 

61 A description of why this connection is deprecated, including the 

62 version after which it may be removed. 

63 

64 If not `None`, the string is appended to the docstring for this 

65 connection and the corresponding config Field. 

66 """ 

67 

68 name: str 

69 storageClass: str 

70 doc: str = "" 

71 multiple: bool = False 

72 deprecated: str | None = dataclasses.field(default=None, kw_only=True) 

73 

74 _connection_type_set: ClassVar[str] 

75 _deprecation_context: str = "" 

76 

77 def __post_init__(self): 

78 if self.deprecated and not self._deprecation_context: 78 ↛ 79line 78 didn't jump to line 79 because the condition on line 78 was never true

79 info = {} 

80 _ = find_outside_stacklevel("lsst.pipe.base", "dataclasses", stack_info=info) 

81 object.__setattr__(self, "_deprecation_context", f"{info['filename']}:{info['lineno']}") 

82 

83 def __get__(self, inst, klass): 

84 """Descriptor access method. 

85 

86 This is a method used to turn a connection into a descriptor. 

87 When a connection is added to a connection class, it is a class level 

88 variable. This method makes accessing this connection, on the 

89 instance of the connection class owning this connection, return a 

90 result specialized for that instance. In the case of connections 

91 this specifically means names specified in a config instance will 

92 be visible instead of the default names for the connection, and that 

93 removed connections will not be accessible on the instance. 

94 """ 

95 # If inst is None, this is being accessed by the class and not an 

96 # instance, return this connection itself 

97 if inst is None: 

98 return self 

99 # Attempt to return the configured connection object from the 

100 # connections instance allConnections mapping. 

101 try: 

102 return inst.allConnections[self.varName] 

103 except KeyError: 

104 raise AttributeError( 

105 f"Connection {self.varName!r} of {klass.__name__} has been removed." 

106 ) from None 

107 

108 

109@dataclasses.dataclass(frozen=True) 

110class DimensionedConnection(BaseConnection): 

111 """Class used for declaring PipelineTask connections that includes 

112 dimensions. 

113 

114 Attributes 

115 ---------- 

116 name : `str` 

117 The name used to identify the dataset type. 

118 storageClass : `str` 

119 The storage class used when (un)/persisting the dataset type. 

120 multiple : `bool` 

121 Indicates if this connection should expect to contain multiple objects 

122 of the given dataset type. Tasks with more than one connection with 

123 ``multiple=True`` with the same dimensions may want to implement 

124 `.PipelineTaskConnections.adjustQuantum` to ensure those datasets are 

125 consistent (i.e. zip-iterable) in `PipelineTask.runQuantum` and notify 

126 the execution system as early as possible of outputs that will not be 

127 produced because the corresponding input is missing. 

128 dimensions : iterable of `str` 

129 The `lsst.daf.butler.Butler` `lsst.daf.butler.Registry` dimensions used 

130 to identify the dataset type identified by the specified name. 

131 isCalibration : `bool`, optional 

132 `True` if this dataset type may be included in CALIBRATION-type 

133 collections to associate it with a validity range, `False` (default) 

134 otherwise. 

135 """ 

136 

137 dimensions: Iterable[str] = () 

138 isCalibration: bool = False 

139 

140 def __post_init__(self): 

141 super().__post_init__() 

142 if isinstance(self.dimensions, str): 142 ↛ 143line 142 didn't jump to line 143 because the condition on line 142 was never true

143 raise TypeError( 

144 "Dimensions must be iterable of dimensions, got str, possibly omitted trailing comma" 

145 ) 

146 if not isinstance(self.dimensions, Iterable): 146 ↛ 147line 146 didn't jump to line 147 because the condition on line 146 was never true

147 raise TypeError("Dimensions must be iterable of dimensions") 

148 

149 

150@dataclasses.dataclass(frozen=True) 

151class BaseInput(DimensionedConnection): 

152 """Class used for declaring PipelineTask input connections. 

153 

154 Attributes 

155 ---------- 

156 name : `str` 

157 The default name used to identify the dataset type. 

158 storageClass : `str` 

159 The storage class used when (un)/persisting the dataset type. 

160 multiple : `bool` 

161 Indicates if this connection should expect to contain multiple objects 

162 of the given dataset type. Tasks with more than one connection with 

163 ``multiple=True`` with the same dimensions may want to implement 

164 `.PipelineTaskConnections.adjustQuantum` to ensure those datasets are 

165 consistent (i.e. zip-iterable) in `PipelineTask.runQuantum` and notify 

166 the execution system as early as possible of outputs that will not be 

167 produced because the corresponding input is missing. 

168 dimensions : iterable of `str` 

169 The `lsst.daf.butler.Butler` `lsst.daf.butler.Registry` dimensions used 

170 to identify the dataset type identified by the specified name. 

171 deferLoad : `bool` 

172 Indicates that this dataset type will be loaded as a 

173 `lsst.daf.butler.DeferredDatasetHandle`. PipelineTasks can use this 

174 object to load the object at a later time. 

175 minimum : `bool` 

176 Minimum number of datasets required for this connection, per quantum. 

177 This is checked in the base implementation of 

178 `.PipelineTaskConnections.adjustQuantum`, which raises `NoWorkFound` if 

179 the minimum is not met for `Input` connections (causing the quantum to 

180 be pruned, skipped, or never created, depending on the context), and 

181 `FileNotFoundError` for `PrerequisiteInput` connections (causing 

182 QuantumGraph generation to fail). `PipelineTask` implementations may 

183 provide custom `~.PipelineTaskConnections.adjustQuantum` 

184 implementations for more fine-grained or configuration-driven 

185 constraints, as long as they are compatible with this minium. 

186 

187 Raises 

188 ------ 

189 TypeError 

190 Raised if ``minimum`` is greater than one but ``multiple=False``. 

191 NotImplementedError 

192 Raised if ``minimum`` is zero for a regular `Input` connection; this 

193 is not currently supported by our QuantumGraph generation algorithm. 

194 """ 

195 

196 deferLoad: bool = False 

197 minimum: int = 1 

198 

199 def __post_init__(self) -> None: 

200 super().__post_init__() 

201 if self.minimum > 1 and not self.multiple: 201 ↛ 202line 201 didn't jump to line 202 because the condition on line 201 was never true

202 raise TypeError(f"Cannot set minimum={self.minimum} if multiple=False.") 

203 

204 

205@dataclasses.dataclass(frozen=True) 

206class Input(BaseInput): 

207 """Class used for declaring PipelineTask input connections. 

208 

209 Attributes 

210 ---------- 

211 name : `str` 

212 The default name used to identify the dataset type. 

213 storageClass : `str` 

214 The storage class used when (un)/persisting the dataset type. 

215 multiple : `bool` 

216 Indicates if this connection should expect to contain multiple objects 

217 of the given dataset type. Tasks with more than one connection with 

218 ``multiple=True`` with the same dimensions may want to implement 

219 `.PipelineTaskConnections.adjustQuantum` to ensure those datasets are 

220 consistent (i.e. zip-iterable) in `PipelineTask.runQuantum` and notify 

221 the execution system as early as possible of outputs that will not be 

222 produced because the corresponding input is missing. 

223 dimensions : iterable of `str` 

224 The `lsst.daf.butler.Butler` `lsst.daf.butler.Registry` dimensions used 

225 to identify the dataset type identified by the specified name. 

226 deferLoad : `bool` 

227 Indicates that this dataset type will be loaded as a 

228 `lsst.daf.butler.DeferredDatasetHandle`. PipelineTasks can use this 

229 object to load the object at a later time. 

230 minimum : `bool` 

231 Minimum number of datasets required for this connection, per quantum. 

232 This is checked in the base implementation of 

233 `.PipelineTaskConnections.adjustQuantum`, which raises `NoWorkFound` if 

234 the minimum is not met for `Input` connections (causing the quantum to 

235 be pruned, skipped, or never created, depending on the context), and 

236 `FileNotFoundError` for `PrerequisiteInput` connections (causing 

237 QuantumGraph generation to fail). `PipelineTask` implementations may 

238 provide custom `~.PipelineTaskConnections.adjustQuantum` 

239 implementations for more fine-grained or configuration-driven 

240 constraints, as long as they are compatible with this minium. 

241 deferGraphConstraint : `bool`, optional 

242 If `True`, do not include this dataset type's existence in the initial 

243 query that starts the QuantumGraph generation process. This can be 

244 used to make QuantumGraph generation faster by avoiding redundant 

245 datasets, and in certain cases it can (along with careful attention to 

246 which tasks are included in the same QuantumGraph) be used to work 

247 around the QuantumGraph generation algorithm's inflexible handling of 

248 spatial overlaps. This option has no effect when the connection is not 

249 an overall input of the pipeline (or subset thereof) for which a graph 

250 is being created, and it never affects the ordering of quanta. 

251 deferBinding : `bool`, optional 

252 If `True`, the dataset will not be automatically included in 

253 the pipeline graph, ``deferGraphConstraint`` is implied. 

254 The custom QuantumGraphBuilder is required to bind it and add a 

255 corresponding edge to the pipeline graph. 

256 This option allows to have the same dataset type as both 

257 input and output of a quantum. 

258 

259 Raises 

260 ------ 

261 TypeError 

262 Raised if ``minimum`` is greater than one but ``multiple=False``. 

263 NotImplementedError 

264 Raised if ``minimum`` is zero for a regular `Input` connection; this 

265 is not currently supported by our QuantumGraph generation algorithm. 

266 """ 

267 

268 deferGraphConstraint: bool = False 

269 

270 deferBinding: bool = False 

271 

272 _connection_type_set: ClassVar[str] = "inputs" 

273 

274 

275@dataclasses.dataclass(frozen=True) 

276class PrerequisiteInput(BaseInput): 

277 """Class used for declaring PipelineTask prerequisite connections. 

278 

279 Attributes 

280 ---------- 

281 name : `str` 

282 The default name used to identify the dataset type. 

283 storageClass : `str` 

284 The storage class used when (un)/persisting the dataset type. 

285 multiple : `bool` 

286 Indicates if this connection should expect to contain multiple objects 

287 of the given dataset type. Tasks with more than one connection with 

288 ``multiple=True`` with the same dimensions may want to implement 

289 `.PipelineTaskConnections.adjustQuantum` to ensure those datasets are 

290 consistent (i.e. zip-iterable) in `PipelineTask.runQuantum` and notify 

291 the execution system as early as possible of outputs that will not be 

292 produced because the corresponding input is missing. 

293 dimensions : iterable of `str` 

294 The `lsst.daf.butler.Butler` `lsst.daf.butler.Registry` dimensions used 

295 to identify the dataset type identified by the specified name. 

296 minimum : `bool` 

297 Minimum number of datasets required for this connection, per quantum. 

298 This is checked in the base implementation of 

299 `.PipelineTaskConnections.adjustQuantum`, which raises 

300 `FileNotFoundError` (causing QuantumGraph generation to fail). 

301 `PipelineTask` implementations may provide custom 

302 `~.PipelineTaskConnections.adjustQuantum` implementations for more 

303 fine-grained or configuration-driven constraints, as long as they are 

304 compatible with this minium. 

305 lookupFunction : `typing.Callable`, optional 

306 An optional callable function that will look up PrerequisiteInputs 

307 using the DatasetType, registry, quantum dataId, and input collections 

308 passed to it. If no function is specified, the default temporal spatial 

309 lookup will be used. 

310 

311 Raises 

312 ------ 

313 TypeError 

314 Raised if ``minimum`` is greater than one but ``multiple=False``. 

315 

316 Notes 

317 ----- 

318 Prerequisite inputs are used for datasets that must exist in the data 

319 repository before a pipeline including this is run; they cannot be produced 

320 by another task in the same pipeline. 

321 

322 In exchange for this limitation, they have a number of advantages relative 

323 to regular `Input` connections: 

324 

325 - The query used to find them then during `QuantumGraph` generation can be 

326 fully customized by providing a ``lookupFunction``. 

327 - Failed searches for prerequisites during `QuantumGraph` generation will 

328 usually generate more helpful diagnostics than those for regular `Input` 

329 connections. 

330 - The default query for prerequisite inputs relates the quantum dimensions 

331 directly to the dimensions of its dataset type, without being constrained 

332 by any of the other dimensions in the pipeline. This allows them to be 

333 used for temporal calibration lookups (which regular `Input` connections 

334 cannot do at present) and to work around `QuantumGraph` generation 

335 limitations involving cases where naive spatial overlap relationships 

336 between dimensions are not desired (e.g. a task that wants all detectors 

337 in each visit for which the visit overlaps a tract, not just those where 

338 that detector+visit combination overlaps the tract). 

339 - Prerequisite inputs may be optional (regular inputs are never optional). 

340 """ 

341 

342 lookupFunction: ( 

343 Callable[[DatasetType, Registry, DataCoordinate, Sequence[str]], Iterable[DatasetRef]] | None 

344 ) = None 

345 

346 _connection_type_set: ClassVar[str] = "prerequisiteInputs" 

347 

348 

349@dataclasses.dataclass(frozen=True) 

350class Output(DimensionedConnection): 

351 """Connection for output dataset.""" 

352 

353 _connection_type_set: ClassVar[str] = "outputs" 

354 

355 

356@dataclasses.dataclass(frozen=True) 

357class InitInput(BaseConnection): 

358 """Connection for initInput dataset.""" 

359 

360 _connection_type_set: ClassVar[str] = "initInputs" 

361 

362 

363@dataclasses.dataclass(frozen=True) 

364class InitOutput(BaseConnection): 

365 """Connection for initOutput dataset.""" 

366 

367 _connection_type_set: ClassVar[str] = "initOutputs"