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-15 02:03 -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/>. 

27 

28from __future__ import annotations 

29 

30__all__ = ("CollectionRecordCache",) 

31 

32from collections.abc import Iterable, Iterator 

33from typing import TYPE_CHECKING, Any 

34 

35if TYPE_CHECKING: 

36 from .interfaces import CollectionRecord 

37 

38 

39class CollectionRecordCache: 

40 """Cache for collection records. 

41 

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. 

49 

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 """ 

54 

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 

60 

61 @property 

62 def full(self) -> bool: 

63 """`True` if cache holds all known collection records (`bool`).""" 

64 return self._full 

65 

66 def add(self, record: CollectionRecord) -> None: 

67 """Add one record to the cache. 

68 

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 

83 

84 def set(self, records: Iterable[CollectionRecord], *, full: bool = False) -> None: 

85 """Replace cache contents with the new set of records. 

86 

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 

99 

100 def clear(self) -> None: 

101 """Remove all records from the cache.""" 

102 self._by_name = {} 

103 self._by_key = {} 

104 self._full = False 

105 

106 def discard(self, record: CollectionRecord) -> None: 

107 """Remove single record from the cache. 

108 

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) 

116 

117 def get_by_name(self, name: str) -> CollectionRecord | None: 

118 """Return collection record given its name. 

119 

120 Parameters 

121 ---------- 

122 name : `str` 

123 Collection name. 

124 

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) 

132 

133 def get_by_key(self, key: Any) -> CollectionRecord | None: 

134 """Return collection record given its key. 

135 

136 Parameters 

137 ---------- 

138 key : `Any` 

139 Collection key. 

140 

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) 

148 

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. 

152 

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())