Coverage for python/lsst/daf/butler/registry/_collection_record_cache.py: 34%
40 statements
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-19 10:53 +0000
« prev ^ index » next coverage.py v7.4.4, created at 2024-04-19 10:53 +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 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 self._by_key[record.key] = record
84 def set(self, records: Iterable[CollectionRecord], *, full: bool = False) -> None:
85 """Replace cache contents with the new set of records.
87 Parameters
88 ----------
89 records : `~collections.abc.Iterable` [`CollectionRecord`]
90 Collection records.
91 full : `bool`
92 If `True` then ``records`` contain all known collection records.
93 """
94 self.clear()
95 for record in records:
96 self._by_name[record.name] = record
97 self._by_key[record.key] = record
98 self._full = full
100 def clear(self) -> None:
101 """Remove all records from the cache."""
102 self._by_name = {}
103 self._by_key = {}
104 self._full = False
106 def discard(self, record: CollectionRecord) -> None:
107 """Remove single record from the cache.
109 Parameters
110 ----------
111 record : `CollectionRecord`
112 Collection record to remove.
113 """
114 self._by_name.pop(record.name, None)
115 self._by_key.pop(record.key, None)
117 def get_by_name(self, name: str) -> CollectionRecord | None:
118 """Return collection record given its name.
120 Parameters
121 ----------
122 name : `str`
123 Collection name.
125 Returns
126 -------
127 record : `CollectionRecord` or `None`
128 Collection record, `None` is returned if the name is not in the
129 cache.
130 """
131 return self._by_name.get(name)
133 def get_by_key(self, key: Any) -> CollectionRecord | None:
134 """Return collection record given its key.
136 Parameters
137 ----------
138 key : `Any`
139 Collection key.
141 Returns
142 -------
143 record : `CollectionRecord` or `None`
144 Collection record, `None` is returned if the key is not in the
145 cache.
146 """
147 return self._by_key.get(key)
149 def records(self) -> Iterator[CollectionRecord]:
150 """Return iterator for the set of records in the cache, can only be
151 used if `full` is true.
153 Raises
154 ------
155 RuntimeError
156 Raised if ``self.full`` is `False`.
157 """
158 if not self._full:
159 raise RuntimeError("cannot call records() if cache is not full")
160 return iter(self._by_name.values())