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 

26from typing import ( 

27 Optional, 

28 Union, 

29) 

30 

31from ._butlerUri import ButlerURI 

32 

33 

34class Location: 

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

36 

37 Parameters 

38 ---------- 

39 datastoreRootUri : `ButlerURI` or `str` or `None` 

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

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

42 path : `ButlerURI` or `str` 

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

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

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

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

47 """ 

48 

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

50 

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

52 path: Union[ButlerURI, str]): 

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

54 path_uri = ButlerURI(path, forceAbsolute=False) 

55 

56 if isinstance(datastoreRootUri, str): 

57 datastoreRootUri = ButlerURI(datastoreRootUri, forceDirectory=True) 

58 elif datastoreRootUri is None: 

59 if not path_uri.isabs(): 

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

61 elif not isinstance(datastoreRootUri, ButlerURI): 

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

63 

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

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

66 

67 self._datastoreRootUri = datastoreRootUri 

68 

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

70 # it is required to be within the root. 

71 if datastoreRootUri is not None: 

72 if path_uri.isabs(): 

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

74 

75 self._path = path_uri 

76 

77 # Internal cache of the full location as a ButlerURI 

78 self._uri: Optional[ButlerURI] = None 

79 

80 # Check that the resulting URI is inside the datastore 

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

82 if self._datastoreRootUri is not None: 

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

84 if pathInStore is None: 

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

86 

87 def __str__(self) -> str: 

88 return str(self.uri) 

89 

90 def __repr__(self) -> str: 

91 uri = self._datastoreRootUri 

92 path = self._path 

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

94 

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

96 if not isinstance(other, Location): 

97 return NotImplemented 

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

99 return self.uri == other.uri 

100 

101 @property 

102 def uri(self) -> ButlerURI: 

103 """Return URI corresponding to fully-specified datastore location.""" 

104 if self._uri is None: 

105 root = self._datastoreRootUri 

106 if root is None: 

107 uri = self._path 

108 else: 

109 uri = root.join(self._path) 

110 self._uri = uri 

111 return self._uri 

112 

113 @property 

114 def path(self) -> str: 

115 """Return path corresponding to location. 

116 

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

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

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

120 with the local OS path separator. 

121 """ 

122 full = self.uri 

123 try: 

124 return full.ospath 

125 except AttributeError: 

126 return full.unquoted_path 

127 

128 @property 

129 def pathInStore(self) -> ButlerURI: 

130 """Return path corresponding to location relative to `Datastore` root. 

131 

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

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

134 """ 

135 return self._path 

136 

137 @property 

138 def netloc(self) -> str: 

139 """Return the URI network location.""" 

140 return self.uri.netloc 

141 

142 @property 

143 def relativeToPathRoot(self) -> str: 

144 """Return the path component relative to the network location. 

145 

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

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

148 """ 

149 return self.uri.relativeToPathRoot 

150 

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

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

153 

154 All file extensions are replaced. 

155 

156 Parameters 

157 ---------- 

158 ext : `str` 

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

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

161 """ 

162 if ext is None: 

163 return 

164 

165 self._path = self._path.updatedExtension(ext) 

166 

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

168 self._uri = None 

169 

170 def getExtension(self) -> str: 

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

172 

173 Returns 

174 ------- 

175 ext : `str` 

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

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

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

179 a value of ``.fits.gz``. 

180 """ 

181 return self.uri.getExtension() 

182 

183 

184class LocationFactory: 

185 """Factory for `Location` instances. 

186 

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

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

189 or as a URI. 

190 

191 Parameters 

192 ---------- 

193 datastoreRoot : `str` 

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

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

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

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

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

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

200 """ 

201 

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

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

204 forceDirectory=True) 

205 

206 def __str__(self) -> str: 

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

208 

209 @property 

210 def netloc(self) -> str: 

211 """Return the network location of root location of the `Datastore`.""" 

212 return self._datastoreRootUri.netloc 

213 

214 def fromPath(self, path: Union[str, ButlerURI]) -> Location: 

215 """Create a `Location` from a POSIX path. 

216 

217 Parameters 

218 ---------- 

219 path : `str` or `ButlerURI` 

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

221 If it is a `ButlerURI` it must not be absolute. 

222 

223 Returns 

224 ------- 

225 location : `Location` 

226 The equivalent `Location`. 

227 """ 

228 path = ButlerURI(path, forceAbsolute=False) 

229 if path.isabs(): 

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

231 return Location(self._datastoreRootUri, path)