Coverage for python / lsst / daf / butler / _dataset_association.py: 55%

32 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-04-28 08:36 +0000

1# This file is part of daf_butler. 

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 

30__all__ = ("DatasetAssociation",) 

31 

32from collections.abc import Iterator, Mapping 

33from dataclasses import dataclass 

34from typing import TYPE_CHECKING, Any 

35 

36from ._collection_type import CollectionType 

37from ._dataset_ref import DatasetRef 

38from ._dataset_type import DatasetType 

39from ._timespan import Timespan 

40 

41if TYPE_CHECKING: 

42 from ._butler_collections import CollectionInfo 

43 from .queries._general_query_results import GeneralQueryResults 

44 

45 

46@dataclass(frozen=True, eq=True) 

47class DatasetAssociation: 

48 """Class representing the membership of a dataset in a single collection. 

49 

50 One dataset is associated with one collection, possibly including 

51 a timespan. 

52 """ 

53 

54 __slots__ = ("ref", "collection", "timespan") 

55 

56 ref: DatasetRef 

57 """Resolved reference to a dataset (`DatasetRef`). 

58 """ 

59 

60 collection: str 

61 """Name of a collection (`str`). 

62 """ 

63 

64 timespan: Timespan | None 

65 """Validity range of the dataset if this is a `~CollectionType.CALIBRATION` 

66 collection (`Timespan` or `None`). 

67 """ 

68 

69 @classmethod 

70 def from_query_result( 

71 cls, 

72 result: GeneralQueryResults, 

73 dataset_type: DatasetType, 

74 collection_info: Mapping[str, CollectionInfo] | None = None, 

75 ) -> Iterator[DatasetAssociation]: 

76 """Construct dataset associations from the result of general query. 

77 

78 Parameters 

79 ---------- 

80 result : `~lsst.daf.butler.queries.GeneralQueryResults` 

81 General query result returned by 

82 `Query.general <lsst.daf.butler.queries.Query.general>` method. The 

83 result has to include "dataset_id", "run", "collection", and 

84 "timespan" dataset fields for ``dataset_type``. 

85 dataset_type : `DatasetType` 

86 Dataset type, query has to include this dataset type. 

87 collection_info : `~collections.abc.Mapping` \ 

88 [`str`, `CollectionInfo`], optional 

89 Mapping from collection name to information about it for all 

90 collections that may appear in the query results. If not provided, 

91 timespans for `~CollectionType.RUN` and `~CollectionType.TAGGED` 

92 collections will be bounded, instead of `None`; this is actually 

93 more consistent with how those timespans are used elsewhere in the 

94 query system, but is a change from how `DatasetAssociation` has 

95 historically worked. 

96 """ 

97 timespan_key = f"{dataset_type.name}.timespan" 

98 collection_key = f"{dataset_type.name}.collection" 

99 for _, refs, row_dict in result.iter_tuples(dataset_type): 

100 collection = row_dict[collection_key] 

101 timespan = row_dict[timespan_key] 

102 if ( 

103 collection_info is not None 

104 and collection_info[collection].type is not CollectionType.CALIBRATION 

105 ): 

106 # This behavior is for backwards compatibility only; in most 

107 # contexts it makes sense to consider the timespan of a RUN 

108 # or TAGGED collection to be unbounded, not None, and that's 

109 # what the query results we're iterating over do. 

110 timespan = None 

111 yield DatasetAssociation(refs[0], collection, timespan) 

112 

113 def __lt__(self, other: Any) -> bool: 

114 # Allow sorting of associations 

115 if not isinstance(other, type(self)): 

116 return NotImplemented 

117 

118 return (self.ref, self.collection, self.timespan) < (other.ref, other.collection, other.timespan)