Coverage for python/lsst/daf/butler/core/_topology.py : 72%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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 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 <http://www.gnu.org/licenses/>.
22from __future__ import annotations
24__all__ = (
25 "TopologicalSpace",
26 "TopologicalFamily",
27 "TopologicalRelationshipEndpoint",
28 "TopologicalExtentDatabaseRepresentation",
29)
31from abc import ABC, abstractmethod
32import enum
33from typing import (
34 Any,
35 Mapping,
36 Optional,
37 Type,
38 TypeVar,
39)
41import sqlalchemy
43from .named import NamedValueAbstractSet
44from .utils import immutable
47@enum.unique
48class TopologicalSpace(enum.Enum):
49 """Enumeration of the different categories of continuous-variable
50 relationships supported by the dimensions system.
52 Most dimension relationships are discrete, in that they are regular foreign
53 key relationships between tables. Those connected to a
54 `TopologicalSpace` are not - a row in a table instead occupies some
55 region in a continuous-variable space, and topological operators like
56 "overlaps" between regions in that space define the relationships between
57 rows.
58 """
60 SPATIAL = enum.auto()
61 """The (spherical) sky, using `lsst.sphgeom.Region` objects to represent
62 those regions in memory.
63 """
65 TEMPORAL = enum.auto()
66 """Time, using `Timespan` instances (with TAI endpoints) to represent
67 intervals in memory.
68 """
71@immutable
72class TopologicalFamily(ABC):
73 """A grouping of `TopologicalRelationshipEndpoint` objects whose regions
74 form a hierarchy in which one endpoint's rows always contain another's in a
75 predefined way.
77 This hierarchy means that endpoints in the same family do not generally
78 have to be have to be related using (e.g.) overlaps; instead, the regions
79 from one "best" endpoint from each family are related to the best endpoint
80 from each other family in a query.
82 Parameters
83 ----------
84 name : `str`
85 Unique string identifier for this family.
86 category : `TopologicalSpace`
87 Space in which the regions of this family live.
88 """
89 def __init__(
90 self,
91 name: str,
92 space: TopologicalSpace,
93 ):
94 self.name = name
95 self.space = space
97 def __eq__(self, other: Any) -> bool:
98 if isinstance(other, TopologicalFamily):
99 return self.space == other.space and self.name == other.name
100 return False
102 def __hash__(self) -> int:
103 return hash(self.name)
105 def __contains__(self, other: TopologicalRelationshipEndpoint) -> bool:
106 return other.topology.get(self.space) == self
108 @abstractmethod
109 def choose(self, endpoints: NamedValueAbstractSet[TopologicalRelationshipEndpoint]
110 ) -> TopologicalRelationshipEndpoint:
111 """Select the best member of this family to use in a query join or
112 data ID when more than one is present.
114 Usually this should correspond to the most fine-grained region.
116 Parameters
117 ----------
118 endpoints : `NamedValueAbstractSet` [`TopologicalRelationshipEndpoint`]
119 Endpoints to choose from. May include endpoints that are not
120 members of this family (which should be ignored).
122 Returns
123 -------
124 best : `TopologicalRelationshipEndpoint`
125 The best endpoint that is both a member of ``self`` and in
126 ``endpoints``.
127 """
128 raise NotImplementedError()
130 name: str
131 """Unique string identifier for this family (`str`).
132 """
134 space: TopologicalSpace
135 """Space in which the regions of this family live (`TopologicalSpace`).
136 """
139@immutable
140class TopologicalRelationshipEndpoint(ABC):
141 """An abstract base class whose instances represent a logical table that
142 may participate in overlap joins defined by a `TopologicalSpace`.
143 """
145 @property
146 @abstractmethod
147 def name(self) -> str:
148 """Unique string identifier for this endpoint (`str`).
149 """
150 raise NotImplementedError()
152 @property
153 @abstractmethod
154 def topology(self) -> Mapping[TopologicalSpace, TopologicalFamily]:
155 """The relationship families to which this endpoint belongs, keyed
156 by the category for that family.
157 """
158 raise NotImplementedError()
160 @property
161 def spatial(self) -> Optional[TopologicalFamily]:
162 """This endpoint's `~TopologicalSpace.SPATIAL` family.
163 """
164 return self.topology.get(TopologicalSpace.SPATIAL)
166 @property
167 def temporal(self) -> Optional[TopologicalFamily]:
168 """This endpoint's `~TopologicalSpace.TEMPORAL` family.
169 """
170 return self.topology.get(TopologicalSpace.TEMPORAL)
173_S = TypeVar("_S", bound="TopologicalExtentDatabaseRepresentation")
176class TopologicalExtentDatabaseRepresentation(ABC):
177 """An abstract base class whose subclasses provide a mapping from the
178 in-memory representation of a `TopologicalSpace` region to a
179 database-storage representation, and whose instances act like a
180 SQLAlchemy-based column expression.
181 """
183 @classmethod
184 @abstractmethod
185 def fromSelectable(cls: Type[_S], selectable: sqlalchemy.sql.FromClause) -> _S:
186 """Construct an instance that represents a logical column (which may
187 actually be backed by multiple columns) in the given table or subquery.
189 Parameters
190 ----------
191 selectable : `sqlalchemy.sql.FromClause`
192 SQLAlchemy object representing a table or subquery.
194 Returns
195 -------
196 representation : `TopologicalExtentDatabaseRepresentation`
197 Object representing a logical column.
198 """
199 raise NotImplementedError()
201 @abstractmethod
202 def overlaps(self: _S, other: _S) -> sqlalchemy.sql.ColumnElement:
203 """Return a boolean SQLAlchemy column expression representing an
204 overlap test between this logical column and another of the same type.
206 Parameters
207 ----------
208 other : ``type(self)``
209 Another instance of the exact same type as ``self``.
211 Returns
212 -------
213 expression : `sqlalchemy.sql.ColumnElement`
214 A boolean SQLAlchemy expression.
215 """
216 raise NotImplementedError()