Coverage for python/lsst/daf/butler/core/location.py : 27%

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/>.
22from __future__ import annotations
24__all__ = ("Location", "LocationFactory")
26import os
27import os.path
28import posixpath
29import types
31from typing import (
32 Optional,
33 Union,
34)
36from ._butlerUri import ButlerURI
39class Location:
40 """Identifies a location within the `Datastore`.
42 Parameters
43 ----------
44 datastoreRootUri : `ButlerURI` or `str`
45 Base URI for this datastore, must include an absolute path.
46 path : `str`
47 Relative path within datastore. Assumed to be using the local
48 path separator if a ``file`` scheme is being used for the URI,
49 else a POSIX separator.
50 """
52 __slots__ = ("_datastoreRootUri", "_path", "_uri")
54 def __init__(self, datastoreRootUri: Union[ButlerURI, str], path: str):
55 if isinstance(datastoreRootUri, str):
56 datastoreRootUri = ButlerURI(datastoreRootUri, forceDirectory=True)
57 elif not isinstance(datastoreRootUri, ButlerURI):
58 raise ValueError("Datastore root must be a ButlerURI instance")
60 if not posixpath.isabs(datastoreRootUri.path):
61 raise ValueError(f"Supplied URI must be an absolute path (given {datastoreRootUri}).")
63 self._datastoreRootUri = datastoreRootUri
65 pathModule: types.ModuleType
66 if not self._datastoreRootUri.scheme:
67 pathModule = os.path
68 else:
69 pathModule = posixpath
71 # mypy can not work out that these modules support isabs
72 if pathModule.isabs(path): # type: ignore
73 raise ValueError("Path within datastore must be relative not absolute")
75 self._path = path
77 # Internal cache of the full location as a ButlerURI
78 self._uri: Optional[ButlerURI] = None
80 def __str__(self) -> str:
81 return str(self.uri)
83 def __repr__(self) -> str:
84 uri = self._datastoreRootUri.geturl()
85 path = self._path
86 return f"{self.__class__.__name__}({uri!r}, {path!r})"
88 @property
89 def uri(self) -> ButlerURI:
90 """URI corresponding to fully-specified location in datastore.
91 """
92 if self._uri is None:
93 self._uri = self._datastoreRootUri.join(self._path)
94 return self._uri
96 @property
97 def path(self) -> str:
98 """Path corresponding to location.
100 This path includes the root of the `Datastore`, but does not include
101 non-path components of the root URI. Paths will not include URI
102 quoting. If a file URI scheme is being used the path will be returned
103 with the local OS path separator.
104 """
105 full = self.uri
106 try:
107 return full.ospath
108 except AttributeError:
109 return full.unquoted_path
111 @property
112 def pathInStore(self) -> str:
113 """Path corresponding to location relative to `Datastore` root.
115 Uses the same path separator as supplied to the object constructor.
116 """
117 return self._path
119 @property
120 def netloc(self) -> str:
121 """The URI network location."""
122 return self._datastoreRootUri.netloc
124 @property
125 def relativeToPathRoot(self) -> str:
126 """Returns the path component of the URI relative to the network
127 location.
129 Effectively, this is the path property with POSIX separator stripped
130 from the left hand side of the path. Will be unquoted.
131 """
132 return self.uri.relativeToPathRoot
134 def updateExtension(self, ext: Optional[str]) -> None:
135 """Update the file extension associated with this `Location`.
137 All file extensions are replaced.
139 Parameters
140 ----------
141 ext : `str`
142 New extension. If an empty string is given any extension will
143 be removed. If `None` is given there will be no change.
144 """
145 if ext is None:
146 return
148 # Get the extension and remove it from the path if one is found
149 # .fits.gz counts as one extension do not use os.path.splitext
150 current = self.getExtension()
151 path = self.pathInStore
152 if current:
153 path = path[:-len(current)]
155 # Ensure that we have a leading "." on file extension (and we do not
156 # try to modify the empty string)
157 if ext and not ext.startswith("."):
158 ext = "." + ext
160 self._path = path + ext
162 # Clear the URI cache so it can be recreated with the new path
163 self._uri = None
165 def getExtension(self) -> str:
166 """Return the file extension(s) associated with this location.
168 Returns
169 -------
170 ext : `str`
171 The file extension (including the ``.``). Can be empty string
172 if there is no file extension. Will return all file extensions
173 as a single extension such that ``file.fits.gz`` will return
174 a value of ``.fits.gz``.
175 """
176 return self.uri.getExtension()
179class LocationFactory:
180 """Factory for `Location` instances.
182 The factory is constructed from the root location of the datastore.
183 This location can be a path on the file system (absolute or relative)
184 or as a URI.
186 Parameters
187 ----------
188 datastoreRoot : `str`
189 Root location of the `Datastore` either as a path in the local
190 filesystem or as a URI. File scheme URIs can be used. If a local
191 filesystem path is used without URI scheme, it will be converted
192 to an absolute path and any home directory indicators expanded.
193 If a file scheme is used with a relative path, the path will
194 be treated as a posixpath but then converted to an absolute path.
195 """
197 def __init__(self, datastoreRoot: str):
198 self._datastoreRootUri = ButlerURI(datastoreRoot, forceAbsolute=True,
199 forceDirectory=True)
201 def __str__(self) -> str:
202 return f"{self.__class__.__name__}@{self._datastoreRootUri}"
204 @property
205 def netloc(self) -> str:
206 """Returns the network location of root location of the `Datastore`."""
207 return self._datastoreRootUri.netloc
209 def fromPath(self, path: str) -> Location:
210 """Factory function to create a `Location` from a POSIX path.
212 Parameters
213 ----------
214 path : `str`
215 A standard POSIX path, relative to the `Datastore` root.
217 Returns
218 -------
219 location : `Location`
220 The equivalent `Location`.
221 """
222 if os.path.isabs(path):
223 raise ValueError("LocationFactory path must be relative to datastore, not absolute.")
224 return Location(self._datastoreRootUri, path)