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/>. 

21 

22from __future__ import annotations 

23 

24__all__ = ("Location", "LocationFactory") 

25 

26import os 

27import os.path 

28 

29from typing import ( 

30 Optional, 

31 Union, 

32) 

33 

34from ._butlerUri import ButlerURI 

35 

36 

37class Location: 

38 """Identifies a location within the `Datastore`. 

39 

40 Parameters 

41 ---------- 

42 datastoreRootUri : `ButlerURI` or `str` or `None` 

43 Base URI for this datastore, must include an absolute path. 

44 If `None` the `path` must correspond to an absolute URI. 

45 path : `ButlerURI` or `str` 

46 Relative path within datastore. Assumed to be using the local 

47 path separator if a ``file`` scheme is being used for the URI, 

48 else a POSIX separator. Can be a full URI if the root URI is `None`. 

49 Can also be a schemeless URI if it refers to a relative path. 

50 """ 

51 

52 __slots__ = ("_datastoreRootUri", "_path", "_uri") 

53 

54 def __init__(self, datastoreRootUri: Union[None, ButlerURI, str], 

55 path: Union[ButlerURI, str]): 

56 # Be careful not to force a relative local path to absolute path 

57 path_uri = ButlerURI(path, forceAbsolute=False) 

58 

59 if isinstance(datastoreRootUri, str): 

60 datastoreRootUri = ButlerURI(datastoreRootUri, forceDirectory=True) 

61 elif datastoreRootUri is None: 

62 if not path_uri.isabs(): 

63 raise ValueError(f"No datastore root URI given but path '{path}' was not absolute URI.") 

64 elif not isinstance(datastoreRootUri, ButlerURI): 

65 raise ValueError("Datastore root must be a ButlerURI instance") 

66 

67 if datastoreRootUri is not None and not datastoreRootUri.isabs(): 

68 raise ValueError(f"Supplied root URI must be an absolute path (given {datastoreRootUri}).") 

69 

70 self._datastoreRootUri = datastoreRootUri 

71 

72 # if the root URI is not None the path must not be absolute since 

73 # it is required to be within the root. 

74 if datastoreRootUri is not None: 

75 if path_uri.isabs(): 

76 raise ValueError(f"Path within datastore must be relative not absolute, got {path_uri}") 

77 

78 self._path = path_uri 

79 

80 # Internal cache of the full location as a ButlerURI 

81 self._uri: Optional[ButlerURI] = None 

82 

83 # Check that the resulting URI is inside the datastore 

84 # This can go wrong if we were given ../dir as path 

85 if self._datastoreRootUri is not None: 

86 pathInStore = self.uri.relative_to(self._datastoreRootUri) 

87 if pathInStore is None: 

88 raise ValueError(f"Unexpectedly {path} jumps out of {self._datastoreRootUri}") 

89 

90 def __str__(self) -> str: 

91 return str(self.uri) 

92 

93 def __repr__(self) -> str: 

94 uri = self._datastoreRootUri 

95 path = self._path 

96 return f"{self.__class__.__name__}({uri!r}, {path.path!r})" 

97 

98 def __eq__(self, other: object) -> bool: 

99 if not isinstance(other, Location): 

100 return NotImplemented 

101 # Compare the combined URI rather than how it is apportioned 

102 return self.uri == other.uri 

103 

104 @property 

105 def uri(self) -> ButlerURI: 

106 """URI corresponding to fully-specified location in datastore. 

107 """ 

108 if self._uri is None: 

109 root = self._datastoreRootUri 

110 if root is None: 

111 uri = self._path 

112 else: 

113 uri = root.join(self._path) 

114 self._uri = uri 

115 return self._uri 

116 

117 @property 

118 def path(self) -> str: 

119 """Path corresponding to location. 

120 

121 This path includes the root of the `Datastore`, but does not include 

122 non-path components of the root URI. Paths will not include URI 

123 quoting. If a file URI scheme is being used the path will be returned 

124 with the local OS path separator. 

125 """ 

126 full = self.uri 

127 try: 

128 return full.ospath 

129 except AttributeError: 

130 return full.unquoted_path 

131 

132 @property 

133 def pathInStore(self) -> ButlerURI: 

134 """Path corresponding to location relative to `Datastore` root. 

135 

136 Uses the same path separator as supplied to the object constructor. 

137 Can be an absolute URI if that is how the location was configured. 

138 """ 

139 return self._path 

140 

141 @property 

142 def netloc(self) -> str: 

143 """The URI network location.""" 

144 return self.uri.netloc 

145 

146 @property 

147 def relativeToPathRoot(self) -> str: 

148 """Returns the path component of the URI relative to the network 

149 location. 

150 

151 Effectively, this is the path property with POSIX separator stripped 

152 from the left hand side of the path. Will be unquoted. 

153 """ 

154 return self.uri.relativeToPathRoot 

155 

156 def updateExtension(self, ext: Optional[str]) -> None: 

157 """Update the file extension associated with this `Location`. 

158 

159 All file extensions are replaced. 

160 

161 Parameters 

162 ---------- 

163 ext : `str` 

164 New extension. If an empty string is given any extension will 

165 be removed. If `None` is given there will be no change. 

166 """ 

167 if ext is None: 

168 return 

169 

170 self._path.updateExtension(ext) 

171 

172 # Clear the URI cache so it can be recreated with the new path 

173 self._uri = None 

174 

175 def getExtension(self) -> str: 

176 """Return the file extension(s) associated with this location. 

177 

178 Returns 

179 ------- 

180 ext : `str` 

181 The file extension (including the ``.``). Can be empty string 

182 if there is no file extension. Will return all file extensions 

183 as a single extension such that ``file.fits.gz`` will return 

184 a value of ``.fits.gz``. 

185 """ 

186 return self.uri.getExtension() 

187 

188 

189class LocationFactory: 

190 """Factory for `Location` instances. 

191 

192 The factory is constructed from the root location of the datastore. 

193 This location can be a path on the file system (absolute or relative) 

194 or as a URI. 

195 

196 Parameters 

197 ---------- 

198 datastoreRoot : `str` 

199 Root location of the `Datastore` either as a path in the local 

200 filesystem or as a URI. File scheme URIs can be used. If a local 

201 filesystem path is used without URI scheme, it will be converted 

202 to an absolute path and any home directory indicators expanded. 

203 If a file scheme is used with a relative path, the path will 

204 be treated as a posixpath but then converted to an absolute path. 

205 """ 

206 

207 def __init__(self, datastoreRoot: Union[ButlerURI, str]): 

208 self._datastoreRootUri = ButlerURI(datastoreRoot, forceAbsolute=True, 

209 forceDirectory=True) 

210 

211 def __str__(self) -> str: 

212 return f"{self.__class__.__name__}@{self._datastoreRootUri}" 

213 

214 @property 

215 def netloc(self) -> str: 

216 """Returns the network location of root location of the `Datastore`.""" 

217 return self._datastoreRootUri.netloc 

218 

219 def fromPath(self, path: str) -> Location: 

220 """Factory function to create a `Location` from a POSIX path. 

221 

222 Parameters 

223 ---------- 

224 path : `str` 

225 A standard POSIX path, relative to the `Datastore` root. 

226 

227 Returns 

228 ------- 

229 location : `Location` 

230 The equivalent `Location`. 

231 """ 

232 if os.path.isabs(path): 

233 raise ValueError("LocationFactory path must be relative to datastore, not absolute.") 

234 return Location(self._datastoreRootUri, path)