LSSTApplications  16.0-10-g0ee56ad+5,16.0-11-ga33d1f2+5,16.0-12-g3ef5c14+3,16.0-12-g71e5ef5+18,16.0-12-gbdf3636+3,16.0-13-g118c103+3,16.0-13-g8f68b0a+3,16.0-15-gbf5c1cb+4,16.0-16-gfd17674+3,16.0-17-g7c01f5c+3,16.0-18-g0a50484+1,16.0-20-ga20f992+8,16.0-21-g0e05fd4+6,16.0-21-g15e2d33+4,16.0-22-g62d8060+4,16.0-22-g847a80f+4,16.0-25-gf00d9b8+1,16.0-28-g3990c221+4,16.0-3-gf928089+3,16.0-32-g88a4f23+5,16.0-34-gd7987ad+3,16.0-37-gc7333cb+2,16.0-4-g10fc685+2,16.0-4-g18f3627+26,16.0-4-g5f3a788+26,16.0-5-gaf5c3d7+4,16.0-5-gcc1f4bb+1,16.0-6-g3b92700+4,16.0-6-g4412fcd+3,16.0-6-g7235603+4,16.0-69-g2562ce1b+2,16.0-8-g14ebd58+4,16.0-8-g2df868b+1,16.0-8-g4cec79c+6,16.0-8-gadf6c7a+1,16.0-8-gfc7ad86,16.0-82-g59ec2a54a+1,16.0-9-g5400cdc+2,16.0-9-ge6233d7+5,master-g2880f2d8cf+3,v17.0.rc1
LSSTDataManagementBasePackage
storage.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 #
4 # LSST Data Management System
5 # Copyright 2016 LSST Corporation.
6 #
7 # This product includes software developed by the
8 # LSST Project (http://www.lsst.org/).
9 #
10 # This program is free software: you can redistribute it and/or modify
11 # it under the terms of the GNU General Public License as published by
12 # the Free Software Foundation, either version 3 of the License, or
13 # (at your option) any later version.
14 #
15 # This program is distributed in the hope that it will be useful,
16 # but WITHOUT ANY WARRANTY; without even the implied warranty of
17 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 # GNU General Public License for more details.
19 #
20 # You should have received a copy of the LSST License Statement and
21 # the GNU General Public License along with this program. If not,
22 # see <http://www.lsstcorp.org/LegalNotices/>.
23 #
24 from __future__ import absolute_import
25 
26 from future import standard_library
27 import urllib.parse
28 from . import NoRepositroyAtRoot
29 standard_library.install_aliases()
30 
31 
32 class Storage:
33  """Base class for storages"""
34 
35  storages = {}
36 
37  def __init__(self):
38  self.repositoryCfgs = {}
39 
40  @staticmethod
41  def registerStorageClass(scheme, cls):
42  """Register derived classes for lookup by URI scheme.
43 
44  A scheme is a name that describes the form a resource at the beginning of a URI
45  e.g. 'http' indicates HTML and related code, such as is found in http://www.lsst.org
46 
47  The only currently supported schemes are:
48  * 'file' where the portion of the URI after the // indicates an absolute locaiton on disk.
49  for example: file:/my_repository_folder/
50  * '' (no scheme) where the entire string is a relative path on the local system
51  for example "my_repository_folder" will indicate a folder in the current working directory with the
52  same name.
53 
54  See documentation for the urlparse python library for more information.
55 
56  .. warning::
57 
58  Storage is 'wet paint' and very likely to change during factorization of Butler back end and
59  storage formats (DM-6225). Use of it in production code other than via the 'old butler' API is
60  strongly discouraged.
61 
62  Parameters
63  ----------
64  scheme : str
65  Name of the `scheme` the class is being registered for, which appears at the beginning of a URI.
66  cls : class object
67  A class object that should be used for a given scheme.
68  """
69  if scheme in Storage.storages:
70  raise RuntimeError("Scheme '%s' already registered:%s" % (scheme, Storage.storages[scheme]))
71  Storage.storages[scheme] = cls
72 
73  def getRepositoryCfg(self, uri):
74  """Get a RepositoryCfg from a location specified by uri.
75 
76  If a cfg is found then it is cached by the uri, so that multiple lookups
77  are not performed on storages that might be remote.
78 
79  RepositoryCfgs are not supposed to change once they are created so this
80  should not lead to stale data.
81  """
82  cfg = self.repositoryCfgs.get(uri, None)
83  if cfg:
84  return cfg
85  parseRes = urllib.parse.urlparse(uri)
86  if parseRes.scheme in Storage.storages:
87  cfg = Storage.storages[parseRes.scheme].getRepositoryCfg(uri)
88  if cfg:
89  self.repositoryCfgs[uri] = cfg
90  else:
91  raise RuntimeError("No storage registered for scheme %s" % parseRes.scheme)
92  return cfg
93 
94  @staticmethod
95  def putRepositoryCfg(cfg, uri):
96  """Write a RepositoryCfg object to a location described by uri"""
97  ret = None
98  parseRes = urllib.parse.urlparse(uri)
99  if parseRes.scheme in Storage.storages:
100  ret = Storage.storages[parseRes.scheme].putRepositoryCfg(cfg, uri)
101  else:
102  raise RuntimeError("No storage registered for scheme %s" % parseRes.scheme)
103  return ret
104 
105  @staticmethod
106  def getMapperClass(uri):
107  """Get a mapper class cfg value from location described by uri.
108 
109  Note that in legacy repositories the mapper may be specified by a file called _mapper at the uri
110  location, and in newer repositories the mapper would be specified by a RepositoryCfg stored at the uri
111  location.
112 
113  .. warning::
114 
115  Storage is 'wet paint' and very likely to change during factorization of Butler back end and
116  storage formats (DM-6225). Use of it in production code other than via the 'old butler' API is
117  strongly discouraged.
118 
119  """
120  ret = None
121  parseRes = urllib.parse.urlparse(uri)
122  if parseRes.scheme in Storage.storages:
123  ret = Storage.storages[parseRes.scheme].getMapperClass(uri)
124  else:
125  raise RuntimeError("No storage registered for scheme %s" % parseRes.scheme)
126  return ret
127 
128  @staticmethod
129  def makeFromURI(uri, create=True):
130  '''Instantiate a StorageInterface sublcass from a URI.
131 
132  .. warning::
133 
134  Storage is 'wet paint' and very likely to change during factorization of Butler back end and
135  storage formats (DM-6225). Use of it in production code other than via the 'old butler' API is
136  strongly discouraged.
137 
138  Parameters
139  ----------
140  uri : string
141  The uri to the root location of a repository.
142  create : bool, optional
143  If True The StorageInterface subclass should create a new
144  repository at the root location. If False then a new repository
145  will not be created.
146 
147  Returns
148  -------
149  A Storage subclass instance, or if create is False and a repository
150  does not exist at the root location then returns None.
151 
152  Raises
153  ------
154  RuntimeError
155  When a StorageInterface subclass does not exist for the scheme
156  indicated by the uri.
157  '''
158  ret = None
159  parseRes = urllib.parse.urlparse(uri)
160  if parseRes.scheme in Storage.storages:
161  theClass = Storage.storages[parseRes.scheme]
162  try:
163  ret = theClass(uri=uri, create=create)
164  except NoRepositroyAtRoot:
165  pass
166  else:
167  raise RuntimeError("No storage registered for scheme %s" % parseRes.scheme)
168  return ret
169 
170  @staticmethod
171  def isPosix(uri):
172  """Test if a URI is for a local filesystem storage.
173 
174  This is mostly for backward compatibility; Butler V1 repositories were only ever on the local
175  filesystem. They may exist but not have a RepositoryCfg class. This enables conditional checking for a
176  V1 Repository.
177 
178  This function treats 'file' and '' (no scheme) as posix storages, see
179  the class docstring for more details.
180 
181  Parameters
182  ----------
183  uri : string
184  URI to the root of a Repository.
185 
186  Returns
187  -------
188  Bool
189  True if the URI is associated with a posix storage, else false.
190  """
191  parseRes = urllib.parse.urlparse(uri)
192  if parseRes.scheme in ('file', ''):
193  return True
194  return False
195 
196  @staticmethod
197  def relativePath(fromUri, toUri):
198  """Get a relative path from a location to a location, if a relative path for these 2 locations exists.
199 
200  Parameters
201  ----------
202  fromPath : string
203  A URI that describes a location at which to start.
204  toPath : string
205  A URI that describes a target location.
206 
207  Returns
208  -------
209  string
210  A relative path that describes the path from fromUri to toUri, provided one exists. If a relative
211  path between the two URIs does not exist then the entire toUri path is returned.
212  """
213  fromUriParseRes = urllib.parse.urlparse(fromUri)
214  toUriParseRes = urllib.parse.urlparse(toUri)
215  if fromUriParseRes.scheme != toUriParseRes.scheme:
216  return toUri
217  storage = Storage.storages.get(fromUriParseRes.scheme, None)
218  if not storage:
219  return toUri
220  return storage.relativePath(fromUri, toUri)
221 
222  @staticmethod
223  def absolutePath(fromUri, toUri):
224  """Get an absolute path for the path from fromUri to toUri
225 
226  Parameters
227  ----------
228  fromUri : the starting location
229  Description
230  toUri : the location relative to fromUri
231  Description
232 
233  Returns
234  -------
235  string
236  URI that is absolutepath fromUri + toUri, if one exists. If toUri is absolute or if fromUri is not
237  related to toUri (e.g. are of different storage types) then toUri will be returned.
238  """
239  fromUriParseRes = urllib.parse.urlparse(fromUri)
240  toUriParseRes = urllib.parse.urlparse(toUri)
241  if fromUriParseRes.scheme != toUriParseRes.scheme:
242  return toUri
243  storage = Storage.storages.get(fromUriParseRes.scheme, None)
244  if not storage:
245  return toUri
246  return storage.absolutePath(fromUri, toUri)
247 
248  @staticmethod
249  def search(uri, path):
250  """Look for the given path in a storage root at URI; return None if it can't be found.
251 
252  If the path contains an HDU indicator (a number in brackets before the
253  dot, e.g. 'foo.fits[1]', this will be stripped when searching and so
254  will match filenames without the HDU indicator, e.g. 'foo.fits'. The
255  path returned WILL contain the indicator though, e.g. ['foo.fits[1]'].
256 
257 
258  Parameters
259  ----------
260  root : string
261  URI to the the root location to search
262  path : string
263  A filename (and optionally prefix path) to search for within root.
264 
265  Returns
266  -------
267  string or None
268  The location that was found, or None if no location was found.
269  """
270  parseRes = urllib.parse.urlparse(uri)
271  storage = Storage.storages.get(parseRes.scheme, None)
272  if storage:
273  return storage.search(uri, path)
274  return None
275 
276  @staticmethod
277  def storageExists(uri):
278  """Ask if a storage at the location described by uri exists
279 
280  Parameters
281  ----------
282  root : string
283  URI to the the root location of the storage
284 
285  Returns
286  -------
287  bool
288  True if the storage exists, false if not
289  """
290  parseRes = urllib.parse.urlparse(uri)
291  storage = Storage.storages.get(parseRes.scheme, None)
292  if storage:
293  return storage.storageExists(uri)
294  return None
def absolutePath(fromUri, toUri)
Definition: storage.py:223
def registerStorageClass(scheme, cls)
Definition: storage.py:41
def makeFromURI(uri, create=True)
Definition: storage.py:129
def relativePath(fromUri, toUri)
Definition: storage.py:197