Coverage for python/lsst/daf/butler/registry/_collection_record_cache.py: 27%
45 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-04 02:55 -0700
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-04 02:55 -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__ = ("CollectionRecordCache",)
32from collections.abc import Iterable, Iterator
33from typing import TYPE_CHECKING, Any
35if TYPE_CHECKING:
36 from .interfaces import CollectionRecord
39class CollectionRecordCache:
40 """Cache for collection records.
42 Notes
43 -----
44 This class stores collection records and can retrieve them using either
45 collection name or collection key. One complication is that key type can be
46 either collection name or a distinct integer value. To optimize storage
47 when the key is the same as collection name, this class only stores key to
48 record mapping when key is of a non-string type.
50 In come contexts (e.g. ``resolve_wildcard``) a full list of collections is
51 needed. To signify that cache content can be used in such contexts, cache
52 defines special ``full`` flag that needs to be set by client.
53 """
55 def __init__(self) -> None:
56 self._by_name: dict[str, CollectionRecord] = {}
57 # This dict is only used for records whose key type is not str.
58 self._by_key: dict[Any, CollectionRecord] = {}
59 self._full = False
61 @property
62 def full(self) -> bool:
63 """`True` if cache holds all known collection records (`bool`)."""
64 return self._full
66 def add(self, record: CollectionRecord) -> None:
67 """Add one record to the cache.
69 Parameters
70 ----------
71 record : `CollectionRecord`
72 Collection record, replaces any existing record with the same name
73 or key.
74 """
75 # In case we replace same record name with different key, find the
76 # existing record and drop its key.
77 if (old_record := self._by_name.get(record.name)) is not None:
78 self._by_key.pop(old_record.key)
79 if (old_record := self._by_key.get(record.key)) is not None:
80 self._by_name.pop(old_record.name)
81 self._by_name[record.name] = record
82 if not isinstance(record.key, str):
83 self._by_key[record.key] = record
85 def set(self, records: Iterable[CollectionRecord], *, full: bool = False) -> None:
86 """Replace cache contents with the new set of records.
88 Parameters
89 ----------
90 records : `~collections.abc.Iterable` [`CollectionRecord`]
91 Collection records.
92 full : `bool`
93 If `True` then ``records`` contain all known collection records.
94 """
95 self.clear()
96 for record in records:
97 self._by_name[record.name] = record
98 if not isinstance(record.key, str):
99 self._by_key[record.key] = record
100 self._full = full
102 def clear(self) -> None:
103 """Remove all records from the cache."""
104 self._by_name = {}
105 self._by_key = {}
106 self._full = False
108 def discard(self, record: CollectionRecord) -> None:
109 """Remove single record from the cache.
111 Parameters
112 ----------
113 record : `CollectionRecord`
114 Collection record to remove.
115 """
116 self._by_name.pop(record.name, None)
117 if not isinstance(record.key, str):
118 self._by_key.pop(record.key, None)
120 def get_by_name(self, name: str) -> CollectionRecord | None:
121 """Return collection record given its name.
123 Parameters
124 ----------
125 name : `str`
126 Collection name.
128 Returns
129 -------
130 record : `CollectionRecord` or `None`
131 Collection record, `None` is returned if the name is not in the
132 cache.
133 """
134 return self._by_name.get(name)
136 def get_by_key(self, key: Any) -> CollectionRecord | None:
137 """Return collection record given its key.
139 Parameters
140 ----------
141 key : `Any`
142 Collection key.
144 Returns
145 -------
146 record : `CollectionRecord` or `None`
147 Collection record, `None` is returned if the key is not in the
148 cache.
149 """
150 if isinstance(key, str):
151 return self._by_name.get(key)
152 return self._by_key.get(key)
154 def records(self) -> Iterator[CollectionRecord]:
155 """Return iterator for the set of records in the cache, can only be
156 used if `full` is true.
158 Raises
159 ------
160 RuntimeError
161 Raised if ``self.full`` is `False`.
162 """
163 if not self._full:
164 raise RuntimeError("cannot call records() if cache is not full")
165 return iter(self._by_name.values())