Coverage for python/lsst/daf/butler/registry/opaque.py : 98%

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/>.
21from __future__ import annotations
22"""The default concrete implementations of the classes that manage
23opaque tables for `Registry`.
24"""
26__all__ = ["ByNameOpaqueTableStorage", "ByNameOpaqueTableStorageManager"]
28from typing import (
29 Any,
30 ClassVar,
31 Dict,
32 Iterable,
33 Iterator,
34 Optional,
35)
37import sqlalchemy
39from ..core.ddl import TableSpec, FieldSpec
40from .interfaces import (
41 Database,
42 OpaqueTableStorageManager,
43 OpaqueTableStorage,
44 StaticTablesContext,
45 VersionTuple
46)
49# This has to be updated on every schema change
50_VERSION = VersionTuple(0, 2, 0)
53class ByNameOpaqueTableStorage(OpaqueTableStorage):
54 """An implementation of `OpaqueTableStorage` that simply creates a true
55 table for each different named opaque logical table.
57 A `ByNameOpaqueTableStorageManager` instance should always be used to
58 construct and manage instances of this class.
60 Parameters
61 ----------
62 db : `Database`
63 Database engine interface for the namespace in which this table lives.
64 name : `str`
65 Name of the logical table (also used as the name of the actual table).
66 table : `sqlalchemy.schema.Table`
67 SQLAlchemy representation of the table, which must have already been
68 created in the namespace managed by ``db`` (this is the responsibility
69 of `ByNameOpaqueTableStorageManager`).
70 """
71 def __init__(self, *, db: Database, name: str, table: sqlalchemy.schema.Table):
72 super().__init__(name=name)
73 self._db = db
74 self._table = table
76 def insert(self, *data: dict) -> None:
77 # Docstring inherited from OpaqueTableStorage.
78 self._db.insert(self._table, *data)
80 def fetch(self, **where: Any) -> Iterator[dict]:
81 # Docstring inherited from OpaqueTableStorage.
82 sql = self._table.select()
83 if where:
84 clauses = []
85 for k, v in where.items():
86 column = self._table.columns[k]
87 if isinstance(v, (list, tuple, set)):
88 clause = column.in_(v)
89 else:
90 clause = column == v
91 clauses.append(clause)
92 sql = sql.where(
93 sqlalchemy.sql.and_(*clauses)
94 )
95 for row in self._db.query(sql):
96 yield dict(row)
98 def delete(self, columns: Iterable[str], *rows: dict) -> None:
99 # Docstring inherited from OpaqueTableStorage.
100 self._db.delete(self._table, columns, *rows)
103class ByNameOpaqueTableStorageManager(OpaqueTableStorageManager):
104 """An implementation of `OpaqueTableStorageManager` that simply creates a
105 true table for each different named opaque logical table.
107 Instances of this class should generally be constructed via the
108 `initialize` class method instead of invoking ``__init__`` directly.
110 Parameters
111 ----------
112 db : `Database`
113 Database engine interface for the namespace in which this table lives.
114 metaTable : `sqlalchemy.schema.Table`
115 SQLAlchemy representation of the table that records which opaque
116 logical tables exist.
117 """
118 def __init__(self, db: Database, metaTable: sqlalchemy.schema.Table):
119 self._db = db
120 self._metaTable = metaTable
121 self._storage: Dict[str, OpaqueTableStorage] = {}
123 _META_TABLE_NAME: ClassVar[str] = "opaque_meta"
125 _META_TABLE_SPEC: ClassVar[TableSpec] = TableSpec(
126 fields=[
127 FieldSpec("table_name", dtype=sqlalchemy.String, length=128, primaryKey=True),
128 ],
129 )
131 @classmethod
132 def initialize(cls, db: Database, context: StaticTablesContext) -> OpaqueTableStorageManager:
133 # Docstring inherited from OpaqueTableStorageManager.
134 metaTable = context.addTable(cls._META_TABLE_NAME, cls._META_TABLE_SPEC)
135 return cls(db=db, metaTable=metaTable)
137 def get(self, name: str) -> Optional[OpaqueTableStorage]:
138 # Docstring inherited from OpaqueTableStorageManager.
139 return self._storage.get(name)
141 def register(self, name: str, spec: TableSpec) -> OpaqueTableStorage:
142 # Docstring inherited from OpaqueTableStorageManager.
143 result = self._storage.get(name)
144 if result is None: 144 ↛ 154line 144 didn't jump to line 154, because the condition on line 144 was never false
145 # Create the table itself. If it already exists but wasn't in
146 # the dict because it was added by another client since this one
147 # was initialized, that's fine.
148 table = self._db.ensureTableExists(name, spec)
149 # Add a row to the meta table so we can find this table in the
150 # future. Also okay if that already exists, so we use sync.
151 self._db.sync(self._metaTable, keys={"table_name": name})
152 result = ByNameOpaqueTableStorage(name=name, table=table, db=self._db)
153 self._storage[name] = result
154 return result
156 @classmethod
157 def currentVersion(cls) -> Optional[VersionTuple]:
158 # Docstring inherited from VersionedExtension.
159 return _VERSION
161 def schemaDigest(self) -> Optional[str]:
162 # Docstring inherited from VersionedExtension.
163 return self._defaultSchemaDigest([self._metaTable], self._db.dialect)