Hide keyboard shortcuts

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 

23__all__ = ( 

24 "allSlots", 

25 "getClassOf", 

26 "getFullTypeName", 

27 "getInstanceOf", 

28 "immutable", 

29 "iterable", 

30 "Singleton", 

31 "stripIfNotNone", 

32 "transactional", 

33) 

34 

35import builtins 

36import functools 

37from typing import ( 

38 Any, 

39 Callable, 

40 Dict, 

41 Iterable, 

42 Iterator, 

43 Mapping, 

44 Optional, 

45 Type, 

46 Union, 

47) 

48 

49from lsst.utils import doImport 

50 

51 

52def iterable(a: Any) -> Iterable[Any]: 

53 """Make input iterable. 

54 

55 There are three cases, when the input is: 

56 

57 - iterable, but not a `str` or Mapping -> iterate over elements 

58 (e.g. ``[i for i in a]``) 

59 - a `str` -> return single element iterable (e.g. ``[a]``) 

60 - a Mapping -> return single element iterable 

61 - not iterable -> return single elment iterable (e.g. ``[a]``). 

62 

63 Parameters 

64 ---------- 

65 a : iterable or `str` or not iterable 

66 Argument to be converted to an iterable. 

67 

68 Returns 

69 ------- 

70 i : `generator` 

71 Iterable version of the input value. 

72 """ 

73 if isinstance(a, str): 

74 yield a 

75 return 

76 if isinstance(a, Mapping): 

77 yield a 

78 return 

79 try: 

80 yield from a 

81 except Exception: 

82 yield a 

83 

84 

85def allSlots(self: Any) -> Iterator[str]: 

86 """ 

87 Return combined ``__slots__`` for all classes in objects mro. 

88 

89 Parameters 

90 ---------- 

91 self : `object` 

92 Instance to be inspected. 

93 

94 Returns 

95 ------- 

96 slots : `itertools.chain` 

97 All the slots as an iterable. 

98 """ 

99 from itertools import chain 

100 return chain.from_iterable(getattr(cls, "__slots__", []) for cls in self.__class__.__mro__) 

101 

102 

103def getFullTypeName(cls: Any) -> str: 

104 """Return full type name of the supplied entity. 

105 

106 Parameters 

107 ---------- 

108 cls : `type` or `object` 

109 Entity from which to obtain the full name. Can be an instance 

110 or a `type`. 

111 

112 Returns 

113 ------- 

114 name : `str` 

115 Full name of type. 

116 

117 Notes 

118 ----- 

119 Builtins are returned without the ``builtins`` specifier included. This 

120 allows `str` to be returned as "str" rather than "builtins.str". 

121 """ 

122 # If we have an instance we need to convert to a type 

123 if not hasattr(cls, "__qualname__"): 123 ↛ 124line 123 didn't jump to line 124, because the condition on line 123 was never true

124 cls = type(cls) 

125 if hasattr(builtins, cls.__qualname__): 125 ↛ 127line 125 didn't jump to line 127, because the condition on line 125 was never true

126 # Special case builtins such as str and dict 

127 return cls.__qualname__ 

128 return cls.__module__ + "." + cls.__qualname__ 

129 

130 

131def getClassOf(typeOrName: Union[Type, str]) -> Type: 

132 """Given the type name or a type, return the python type. 

133 

134 If a type name is given, an attempt will be made to import the type. 

135 

136 Parameters 

137 ---------- 

138 typeOrName : `str` or Python class 

139 A string describing the Python class to load or a Python type. 

140 

141 Returns 

142 ------- 

143 type_ : `type` 

144 Directly returns the Python type if a type was provided, else 

145 tries to import the given string and returns the resulting type. 

146 

147 Notes 

148 ----- 

149 This is a thin wrapper around `~lsst.utils.doImport`. 

150 """ 

151 if isinstance(typeOrName, str): 

152 cls = doImport(typeOrName) 

153 else: 

154 cls = typeOrName 

155 return cls 

156 

157 

158def getInstanceOf(typeOrName: Union[Type, str], *args: Any, **kwargs: Any) -> Any: 

159 """Given the type name or a type, instantiate an object of that type. 

160 

161 If a type name is given, an attempt will be made to import the type. 

162 

163 Parameters 

164 ---------- 

165 typeOrName : `str` or Python class 

166 A string describing the Python class to load or a Python type. 

167 args : `tuple` 

168 Positional arguments to use pass to the object constructor. 

169 kwargs : `dict` 

170 Keyword arguments to pass to object constructor. 

171 

172 Returns 

173 ------- 

174 instance : `object` 

175 Instance of the requested type, instantiated with the provided 

176 parameters. 

177 """ 

178 cls = getClassOf(typeOrName) 

179 return cls(*args, **kwargs) 

180 

181 

182class Singleton(type): 

183 """Metaclass to convert a class to a Singleton. 

184 

185 If this metaclass is used the constructor for the singleton class must 

186 take no arguments. This is because a singleton class will only accept 

187 the arguments the first time an instance is instantiated. 

188 Therefore since you do not know if the constructor has been called yet it 

189 is safer to always call it with no arguments and then call a method to 

190 adjust state of the singleton. 

191 """ 

192 

193 _instances: Dict[Type, Any] = {} 

194 

195 # Signature is intentionally not substitutable for type.__call__ (no *args, 

196 # **kwargs) to require classes that use this metaclass to have no 

197 # constructor arguments. 

198 def __call__(cls) -> Any: # type: ignore 

199 if cls not in cls._instances: 

200 cls._instances[cls] = super(Singleton, cls).__call__() 

201 return cls._instances[cls] 

202 

203 

204def transactional(func: Callable) -> Callable: 

205 """Decorator that wraps a method and makes it transactional. 

206 

207 This depends on the class also defining a `transaction` method 

208 that takes no arguments and acts as a context manager. 

209 """ 

210 @functools.wraps(func) 

211 def inner(self: Any, *args: Any, **kwargs: Any) -> Any: 

212 with self.transaction(): 

213 return func(self, *args, **kwargs) 

214 return inner 

215 

216 

217def stripIfNotNone(s: Optional[str]) -> Optional[str]: 

218 """Strip leading and trailing whitespace if the given object is not None. 

219 

220 Parameters 

221 ---------- 

222 s : `str`, optional 

223 Input string. 

224 

225 Returns 

226 ------- 

227 r : `str` or `None` 

228 A string with leading and trailing whitespace stripped if `s` is not 

229 `None`, or `None` if `s` is `None`. 

230 """ 

231 if s is not None: 

232 s = s.strip() 

233 return s 

234 

235 

236def immutable(cls: Type) -> Type: 

237 """A class decorator that simulates a simple form of immutability for 

238 the decorated class. 

239 

240 A class decorated as `immutable` may only set each of its attributes once 

241 (by convention, in ``__new__``); any attempts to set an already-set 

242 attribute will raise `AttributeError`. 

243 

244 Because this behavior interferes with the default implementation for 

245 the ``pickle`` and ``copy`` modules, `immutable` provides implementations 

246 of ``__getstate__`` and ``__setstate__`` that override this behavior. 

247 Immutable classes can them implement pickle/copy via ``__getnewargs__`` 

248 only (other approaches such as ``__reduce__`` and ``__deepcopy__`` may 

249 also be used). 

250 """ 

251 def __setattr__(self: Any, name: str, value: Any) -> None: # noqa: N807 

252 if hasattr(self, name): 

253 raise AttributeError(f"{cls.__name__} instances are immutable.") 

254 object.__setattr__(self, name, value) 

255 # mypy says the variable here has signature (str, Any) i.e. no "self"; 

256 # I think it's just confused by descriptor stuff. 

257 cls.__setattr__ = __setattr__ # type: ignore 

258 

259 def __getstate__(self: Any) -> dict: # noqa: N807 

260 # Disable default state-setting when unpickled. 

261 return {} 

262 cls.__getstate__ = __getstate__ 

263 

264 def __setstate__(self: Any, state: Any) -> None: # noqa: N807 

265 # Disable default state-setting when copied. 

266 # Sadly what works for pickle doesn't work for copy. 

267 assert not state 

268 cls.__setstate__ = __setstate__ 

269 return cls