Coverage for python/lsst/dax/apdb/apdbReplica.py: 95%
60 statements
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 09:42 +0000
« prev ^ index » next coverage.py v7.17.2, created at 2026-11-07 09:42 +0000
1# This file is part of dax_apdb.
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__ = ["ApdbReplica", "ApdbTableData", "ReplicaChunk"]
26import uuid
27from abc import ABC, abstractmethod
28from collections.abc import Collection, Iterable, Sequence
29from dataclasses import dataclass
30from typing import TYPE_CHECKING
32import astropy.time
33import felis.datamodel
35from lsst.resources import ResourcePathExpression
37from .apdb import ApdbConfig, ApdbTables
38from .factory import make_apdb_replica
40if TYPE_CHECKING:
41 from .apdbUpdateRecord import ApdbUpdateRecord
42 from .versionTuple import VersionTuple
45class ApdbTableData(ABC):
46 """Abstract class for representing table data."""
48 @abstractmethod
49 def column_names(self) -> Sequence[str]:
50 """Return ordered sequence of column names in the table.
52 Returns
53 -------
54 names : `~collections.abc.Sequence` [`str`]
55 Column names.
56 """
57 raise NotImplementedError()
59 @abstractmethod
60 def column_defs(self) -> Sequence[tuple[str, felis.datamodel.DataType]]:
61 """Return ordered sequence of column names and their types.
63 Returns
64 -------
65 columns : `~collections.abc.Sequence` \
66 [`tuple`[`str`, `felis.datamodel.DataType`]]
67 Sequence of 2-tuples, each tuple consists of column name and its
68 type.
69 """
70 raise NotImplementedError()
72 @abstractmethod
73 def rows(self) -> Collection[tuple]:
74 """Return table rows, each row is a tuple of values.
76 Returns
77 -------
78 rows : `~collections.abc.Collection` [`tuple`]
79 Collection of tuples.
80 """
81 raise NotImplementedError()
84@dataclass(frozen=True)
85class ReplicaChunk:
86 """Class used for identification of replication chunks.
88 Instances of this class are used to identify the units of transfer from
89 APDB to PPDB. Usually single `ReplicaChunk` corresponds to multiple
90 consecutive calls to `Apdb.store` method.
92 Every ``store`` with the same ``id`` value will update ``unique_id`` with
93 some unique value so that it can be verified on PPDB side.
94 """
96 id: int
97 """A number identifying replication chunk (`int`)."""
99 last_update_time: astropy.time.Time
100 """Time of last insert for this chunk, usually corresponds to visit time
101 (`astropy.time.Time`).
102 """
104 unique_id: uuid.UUID
105 """Unique value updated on each new store (`uuid.UUID`)."""
107 @classmethod
108 def make_replica_chunk(
109 cls, last_update_time: astropy.time.Time, chunk_window_seconds: int
110 ) -> ReplicaChunk:
111 """Generate new unique insert identifier."""
112 seconds = int(last_update_time.unix_tai)
113 seconds = (seconds // chunk_window_seconds) * chunk_window_seconds
114 unique_id = uuid.uuid4()
115 return ReplicaChunk(id=seconds, last_update_time=last_update_time, unique_id=unique_id)
117 def __str__(self) -> str:
118 class_name = self.__class__.__name__
119 time_str = str(self.last_update_time.tai.isot)
120 return f"{class_name}(id={self.id:10d}, last_update_time={time_str}/tai, unique_id={self.unique_id})"
123class ApdbReplica(ABC):
124 """Abstract interface for APDB replication methods."""
126 @classmethod
127 def from_config(cls, config: ApdbConfig) -> ApdbReplica:
128 """Create ApdbReplica instance from configuration object.
130 Parameters
131 ----------
132 config : `ApdbConfig`
133 Configuration object, type of this object determines type of the
134 ApdbReplica implementation.
136 Returns
137 -------
138 replica : `ApdbReplica`
139 Instance of `ApdbReplica` class.
140 """
141 return make_apdb_replica(config)
143 @classmethod
144 def from_uri(cls, uri: ResourcePathExpression) -> ApdbReplica:
145 """Make ApdbReplica instance from a serialized configuration.
147 Parameters
148 ----------
149 uri : `~lsst.resources.ResourcePathExpression`
150 URI or local file path pointing to a file with serialized
151 configuration, or a string with a "label:" prefix. In the latter
152 case, the configuration will be looked up from an APDB index file
153 using the label name that follows the prefix. The APDB index file's
154 location is determined by the ``DAX_APDB_INDEX_URI`` environment
155 variable.
157 Returns
158 -------
159 replica : `ApdbReplica`
160 Instance of `ApdbReplica` class, the type of the returned instance
161 is determined by configuration.
162 """
163 config = ApdbConfig.from_uri(uri)
164 return make_apdb_replica(config)
166 @abstractmethod
167 def close(self) -> None:
168 """Release all resources associated with this instance. The instance
169 may no longer be used after this is called.
170 """
171 raise NotImplementedError()
173 @classmethod
174 @abstractmethod
175 def apdbReplicaImplementationVersion(cls) -> VersionTuple:
176 """Return version number for current ApdbReplica implementation.
178 Returns
179 -------
180 version : `VersionTuple`
181 Version of the code defined in implementation class.
182 """
183 raise NotImplementedError()
185 @abstractmethod
186 def schemaVersion(self) -> VersionTuple:
187 """Return version number of the database schema.
189 Returns
190 -------
191 version : `VersionTuple`
192 Version of the database schema.
193 """
194 raise NotImplementedError()
196 @abstractmethod
197 def getReplicaChunks(self) -> list[ReplicaChunk] | None:
198 """Return collection of replication chunks known to the database.
200 Returns
201 -------
202 chunks : `list` [`ReplicaChunk`] or `None`
203 List of chunks, they may be time-ordered if database supports
204 ordering. `None` is returned if database is not configured for
205 replication.
206 """
207 raise NotImplementedError()
209 @abstractmethod
210 def deleteReplicaChunks(self, chunks: Iterable[int]) -> None:
211 """Remove replication chunks from the database.
213 Parameters
214 ----------
215 chunks : `~collections.abc.Iterable` [`int`]
216 Chunk identifiers to remove.
218 Notes
219 -----
220 This method causes Apdb to forget about specified chunks. If there
221 are any auxiliary data associated with the identifiers, it is also
222 removed from database (but data in regular tables is not removed).
223 This method should be called after successful transfer of data from
224 APDB to PPDB to free space used by replicas.
225 """
226 raise NotImplementedError()
228 @abstractmethod
229 def getTableDataChunks(self, table: ApdbTables, chunks: Iterable[int]) -> ApdbTableData:
230 """Return catalog of new records for a table from given replica chunks.
232 Parameters
233 ----------
234 table : `ApdbTables`
235 Table for which to return the data. Acceptable tables are
236 `ApdbTables.DiaObject`, `ApdbTables.DiaSource`, and
237 `ApdbTables.DiaForcedSource`.
238 chunks : `~collections.abc.Iterable` [`int`]
239 Chunk identifiers to return.
241 Returns
242 -------
243 data : `ApdbTableData`
244 Catalog containing table records. In addition to all regular
245 columns it will contain ``apdb_replica_chunk`` column.
247 Notes
248 -----
249 This method returns new records that have been added to the table by
250 `Apdb.store()` method. Updates to the records that happen at later time
251 are available from `getTableUpdateChunks` method.
253 This part of API may not be very stable and can change before the
254 implementation finalizes.
255 """
256 raise NotImplementedError()
258 @abstractmethod
259 def getUpdateRecordChunks(self, chunks: Iterable[int]) -> Sequence[ApdbUpdateRecord]:
260 """Return the list of record updates from given replica chunks.
262 Parameters
263 ----------
264 chunks : `~collections.abc.Iterable` [`int`]
265 Chunk identifiers to return.
267 Returns
268 -------
269 records : `~collections.abc.Sequence` [`ApdbUpdateRecord`]
270 Collection of update records. Records will be sorted according
271 their update time and update order.
272 """
273 raise NotImplementedError()