Coverage for python/lsst/afw/typehandling/_GenericMap.py: 25%

61 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-04-15 02:29 -0700

1# This file is part of afw. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

5# (https://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 <https://www.gnu.org/licenses/>. 

21 

22__all__ = ["GenericMap", "MutableGenericMap"] 

23 

24from collections.abc import Mapping, MutableMapping 

25 

26from lsst.utils import TemplateMeta 

27from ._typehandling import GenericMapS, MutableGenericMapS 

28 

29 

30class GenericMap(metaclass=TemplateMeta): 

31 """An abstract `~collections.abc.Mapping` for use when sharing a 

32 map between C++ and Python. 

33 

34 For compatibility with C++, ``GenericMap`` has the following 

35 restrictions: 

36 

37 - all keys must be of the same type 

38 - values must be built-in types or subclasses of 

39 `lsst.afw.typehandling.Storable`. Almost any user-defined class in 

40 C++ or Python can have `~lsst.afw.typehandling.Storable` as a mixin. 

41 

42 As a safety precaution, `~lsst.afw.typehandling.Storable` objects that are 

43 added from C++ may be copied when you retrieve them from Python, making it 

44 impossible to modify them in-place. This issue does not affect objects that 

45 are added from Python, or objects that are always passed by 

46 :cpp:class:`shared_ptr` in C++. 

47 """ 

48 

49 def __repr__(self): 

50 className = type(self).__name__ 

51 return className + "({" + ", ".join("%r: %r" % (key, value) for key, value in self.items()) + "})" 

52 

53 # Support equality with any Mapping, including dict 

54 # Not clear why Mapping.__eq__ doesn't work 

55 def __eq__(self, other): 

56 if len(self) != len(other): 

57 return False 

58 

59 for key, value in self.items(): 

60 try: 

61 if (value != other[key]): 

62 return False 

63 except KeyError: 

64 return False 

65 return True 

66 

67 # Easier than making GenericMap actually inherit from Mapping 

68 keys = Mapping.keys 

69 values = Mapping.values 

70 items = Mapping.items 

71 

72 

73GenericMap.register(str, GenericMapS) 

74Mapping.register(GenericMapS) 

75 

76 

77class MutableGenericMap(GenericMap): 

78 """An abstract `~collections.abc.MutableMapping` for use when sharing a 

79 map between C++ and Python. 

80 

81 For compatibility with C++, ``MutableGenericMap`` has the following 

82 restrictions: 

83 

84 - all keys must be of the same type 

85 - values must be built-in types or subclasses of 

86 `lsst.afw.typehandling.Storable`. Almost any user-defined class in 

87 C++ or Python can have `~lsst.afw.typehandling.Storable` as a mixin. 

88 

89 As a safety precaution, `~lsst.afw.typehandling.Storable` objects that are 

90 added from C++ may be copied when you retrieve them from Python, making it 

91 impossible to modify them in-place. This issue does not affect objects that 

92 are added from Python, or objects that are always passed by 

93 :cpp:class:`shared_ptr` in C++. 

94 

95 Notes 

96 ----- 

97 Key-type specializations of ``MutableGenericMap`` are available as, e.g., 

98 ``MutableGenericMap[str]``. 

99 """ 

100 

101 # Easier than making MutableGenericMap actually inherit from MutableMapping 

102 setdefault = MutableMapping.setdefault 

103 update = MutableMapping.update 

104 

105 # MutableMapping.pop relies on implementation details of MutableMapping 

106 def pop(self, key, default=None): 

107 try: 

108 value = self[key] 

109 del self[key] 

110 return value 

111 except KeyError: 

112 if default is not None: 

113 return default 

114 else: 

115 raise 

116 

117 

118MutableGenericMap.register(str, MutableGenericMapS) 

119MutableMapping.register(MutableGenericMapS) 

120 

121 

122class AutoKeyMeta(TemplateMeta): 

123 """A metaclass for abstract mappings whose key type is implied by their 

124 constructor arguments. 

125 

126 This metaclass requires that the mapping have a `dict`-like constructor, 

127 i.e., it takes a mapping or an iterable of key-value pairs as its first 

128 positional parameter. 

129 

130 This class differs from `~lsst.utils.TemplateMeta` only in that the dtype 

131 (or equivalent) constructor keyword is optional. If it is omitted, the 

132 class will attempt to infer it from the first argument. 

133 """ 

134 

135 def __call__(cls, *args, **kwargs): # noqa N805, non-self first param 

136 if len(cls.TEMPLATE_PARAMS) != 1: 

137 raise ValueError("AutoKeyMeta requires exactly one template parameter") 

138 dtypeKey = cls.TEMPLATE_PARAMS[0] 

139 dtype = kwargs.get(dtypeKey, None) 

140 

141 # Try to infer dtype if not provided 

142 if dtype is None and len(args) >= 1: 

143 dtype = cls._guessKeyType(args[0]) 

144 if dtype is not None: 

145 kwargs[dtypeKey] = dtype 

146 

147 return super().__call__(*args, **kwargs) 

148 

149 def _guessKeyType(cls, inputData): # noqa N805, non-self first param 

150 """Try to infer the key type of a map from its input. 

151 

152 Parameters 

153 ---------- 

154 inputData : `~collections.abc.Mapping` or iterable of pairs 

155 Any object that can be passed to a `dict`-like constructor. Keys 

156 are assumed homogeneous (if not, a 

157 `~lsst.afw.typehandling.GenericMap` constructor will raise 

158 `TypeError` no matter what key type, if any, is provided). 

159 

160 Returns 

161 ------- 

162 keyType : `type` 

163 The type of the keys in ``inputData``, or `None` if the type could 

164 not be inferred. 

165 """ 

166 if inputData: 

167 firstKey = None 

168 if isinstance(inputData, Mapping): 

169 # mapping to copy 

170 firstKey = iter(inputData.keys()).__next__() 

171 elif not isinstance(inputData, str): 

172 # iterable of key-value pairs 

173 try: 

174 firstKey = iter(inputData).__next__()[0] 

175 except TypeError: 

176 # Not an iterable of pairs 

177 pass 

178 if firstKey: 

179 return type(firstKey) 

180 # Any other input is either empty or an invalid input to dict-like constructors 

181 return None