Coverage for python/lsst/daf/butler/registry/obscore/_spatial.py: 42%
43 statements
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-05 10:36 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2023-01-05 10: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 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__ = ["MissingDatabaseError", "RegionTypeWarning", "SpatialObsCorePlugin"]
26from abc import ABC, abstractmethod
27from collections.abc import Mapping, Sequence
28from typing import TYPE_CHECKING, Any, Optional
30from lsst.daf.butler import DatasetId
31from lsst.sphgeom import Region
32from lsst.utils import doImportType
34if TYPE_CHECKING: 34 ↛ 35line 34 didn't jump to line 35, because the condition on line 34 was never true
35 from ...core import ddl
36 from ..interfaces import Database
37 from ._config import SpatialPluginConfig
38 from ._records import Record
41class MissingDatabaseError(Exception):
42 """Exception raised when database is not provided but plugin implementation
43 requires it.
44 """
47class RegionTypeWarning(Warning):
48 """Warning category for unsupported region types."""
51class SpatialObsCorePlugin(ABC):
52 """Interface for classes that implement support for spatial columns and
53 indices in obscore table.
54 """
56 @classmethod
57 @abstractmethod
58 def initialize(
59 cls, *, name: str, config: Mapping[str, Any], db: Optional[Database]
60 ) -> SpatialObsCorePlugin:
61 """Construct an instance of the plugin.
63 Parameters
64 ----------
65 name : `str`
66 Arbitrary name given to this plugin (usually key in
67 configuration).
68 config : `dict` [ `str`, `Any` ]
69 Plugin configuration dictionary.
70 db : `Database`, optional
71 Interface to the underlying database engine and namespace. In some
72 contexts the database may not be available and will be set to
73 `None`. If plugin class requires access to the database, it should
74 raise a `MissingDatabaseError` exception when ``db`` is `None`.
76 Returns
77 -------
78 manager : `ObsCoreTableManager`
79 Plugin instance.
81 Raises
82 ------
83 MissingDatabaseError
84 Raised if plugin requires access to database, but ``db`` is `None`.
85 """
86 raise NotImplementedError()
88 @abstractmethod
89 def extend_table_spec(self, table_spec: ddl.TableSpec) -> None:
90 """Update obscore table specification with any new columns that are
91 needed for this plugin.
93 Parameters
94 ----------
95 table : `ddl.TableSpec`
96 ObsCore table specification.
98 Notes
99 -----
100 Table specification is updated in place. Plugins should not remove
101 columns, normally updates are limited to adding new columns or indices.
102 """
103 raise NotImplementedError()
105 @abstractmethod
106 def make_records(self, dataset_id: DatasetId, region: Optional[Region]) -> Optional[Record]:
107 """Return data for obscore records corresponding to a given region.
109 Parameters
110 ----------
111 dataset_id : `DatasetId`
112 ID of the corresponding dataset.
113 region : `Region`, optional
114 Spatial region, can be `None` if dataset has no associated region.
116 Returns
117 -------
118 record : `dict` [ `str`, `Any` ] or `None`
119 Data to store in the main obscore table with column values
120 corresponding to a region or `None` if there is nothing to store.
121 """
122 raise NotImplementedError()
124 @classmethod
125 def load_plugins(
126 cls, config: Mapping[str, SpatialPluginConfig], db: Optional[Database]
127 ) -> Sequence[SpatialObsCorePlugin]:
128 """Load all plugins based on plugin configurations.
130 Parameters
131 ----------
132 config : `Mapping` [ `str`, `SpatialPluginConfig` ]
133 Configuration for plugins. The key is an arbitrary name and the
134 value is an object describing plugin class and its configuration
135 options.
136 db : `Database`, optional
137 Interface to the underlying database engine and namespace.
139 Raises
140 ------
141 MissingDatabaseError
142 Raised if one of the plugins requires access to database, but
143 ``db`` is `None`.
144 """
145 # We have to always load default plugin even if it does not appear in
146 # configuration.
147 from .default_spatial import DefaultSpatialObsCorePlugin
149 spatial_plugins: list[SpatialObsCorePlugin] = []
150 has_default = False
151 for plugin_name, plugin_config in config.items():
152 class_name = plugin_config.cls
153 plugin_class = doImportType(class_name)
154 if not issubclass(plugin_class, SpatialObsCorePlugin):
155 raise TypeError(
156 f"Spatial ObsCore plugin {class_name} is not a subclass of SpatialObsCorePlugin"
157 )
158 plugin = plugin_class.initialize(name=plugin_name, config=plugin_config.config, db=db)
159 spatial_plugins.append(plugin)
160 if plugin_class is DefaultSpatialObsCorePlugin:
161 has_default = True
163 if not has_default:
164 # Always create default spatial plugin.
165 spatial_plugins.insert(
166 0, DefaultSpatialObsCorePlugin.initialize(name="default", config={}, db=db)
167 )
169 return spatial_plugins