Coverage for python/lsst/daf/butler/_column_tags.py: 74%
70 statements
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-07 02:46 -0700
« prev ^ index » next coverage.py v7.5.1, created at 2024-05-07 02:46 -0700
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/>.
28from __future__ import annotations
30__all__ = (
31 "DatasetColumnTag",
32 "DimensionKeyColumnTag",
33 "DimensionRecordColumnTag",
34 "is_timespan_column",
35)
37import dataclasses
38from collections.abc import Iterable
39from typing import TYPE_CHECKING, Any, TypeVar, final
41_S = TypeVar("_S")
43if TYPE_CHECKING:
44 from lsst.daf.relation import ColumnTag
47class _BaseColumnTag:
48 __slots__ = ()
50 @classmethod
51 def filter_from(cls: type[_S], tags: Iterable[Any]) -> set[_S]:
52 return {tag for tag in tags if type(tag) is cls}
55@final
56@dataclasses.dataclass(frozen=True, slots=True)
57class DimensionKeyColumnTag(_BaseColumnTag):
58 """An identifier for `~lsst.daf.relation.Relation` columns that represent
59 a dimension primary key value.
60 """
62 dimension: str
63 """Name of the dimension (`str`)."""
65 def __str__(self) -> str:
66 return self.dimension
68 @property
69 def qualified_name(self) -> str:
70 return self.dimension
72 @property
73 def is_key(self) -> bool:
74 return True
76 @classmethod
77 def generate(cls, dimensions: Iterable[str]) -> list[DimensionKeyColumnTag]:
78 """Return a list of column tags from an iterable of dimension
79 names.
81 Parameters
82 ----------
83 dimensions : `~collections.abc.Iterable` [ `str` ]
84 Dimension names.
86 Returns
87 -------
88 tags : `list` [ `DimensionKeyColumnTag` ]
89 List of column tags.
90 """
91 return [cls(d) for d in dimensions]
94@final
95@dataclasses.dataclass(frozen=True, slots=True)
96class DimensionRecordColumnTag(_BaseColumnTag):
97 """An identifier for `~lsst.daf.relation.Relation` columns that represent
98 non-key columns in a dimension or dimension element record.
99 """
101 element: str
102 """Name of the dimension element (`str`).
103 """
105 column: str
106 """Name of the column (`str`)."""
108 def __str__(self) -> str:
109 return f"{self.element}.{self.column}"
111 @property
112 def qualified_name(self) -> str:
113 return f"n!{self.element}:{self.column}"
115 @property
116 def is_key(self) -> bool:
117 return False
119 @classmethod
120 def generate(cls, element: str, columns: Iterable[str]) -> list[DimensionRecordColumnTag]:
121 """Return a list of column tags from an iterable of column names
122 for a single dimension element.
124 Parameters
125 ----------
126 element : `str`
127 Name of the dimension element.
128 columns : `~collections.abc.Iterable` [ `str` ]
129 Column names.
131 Returns
132 -------
133 tags : `list` [ `DimensionRecordColumnTag` ]
134 List of column tags.
135 """
136 return [cls(element, column) for column in columns]
139@final
140@dataclasses.dataclass(frozen=True, slots=True)
141class DatasetColumnTag(_BaseColumnTag):
142 """An identifier for `~lsst.daf.relation.Relation` columns that represent
143 columns from a dataset query or subquery.
144 """
146 dataset_type: str
147 """Name of the dataset type (`str`)."""
149 column: str
150 """Name of the column (`str`).
152 Allowed values are:
154 - "dataset_id" (autoincrement or UUID primary key)
155 - "run" (collection primary key, not collection name)
156 - "ingest_date"
157 - "timespan" (validity range, or NULL for non-calibration collections)
158 - "rank" (collection position in ordered search)
159 """
161 def __str__(self) -> str:
162 return f"{self.dataset_type}.{self.column}"
164 @property
165 def qualified_name(self) -> str:
166 return f"t!{self.dataset_type}:{self.column}"
168 @property
169 def is_key(self) -> bool:
170 return self.column == "dataset_id" or self.column == "run"
172 @classmethod
173 def generate(cls, dataset_type: str, columns: Iterable[str]) -> list[DatasetColumnTag]:
174 """Return a list of column tags from an iterable of column names
175 for a single dataset type.
177 Parameters
178 ----------
179 dataset_type : `str`
180 Name of the dataset type.
181 columns : `~collections.abc.Iterable` [ `str` ]
182 Column names.
184 Returns
185 -------
186 tags : `list` [ `DatasetColumnTag` ]
187 List of column tags.
188 """
189 return [cls(dataset_type, column) for column in columns]
192def is_timespan_column(tag: ColumnTag) -> bool:
193 """Test whether a column tag is a timespan.
195 Parameters
196 ----------
197 tag : `ColumnTag`
198 Column tag to test.
200 Returns
201 -------
202 is_timespan : `bool`
203 Whether the given column is a timespan.
204 """
205 match tag:
206 case DimensionRecordColumnTag(column="timespan"):
207 return True
208 case DatasetColumnTag(column="timespan"):
209 return True
210 return False