LSSTApplications  11.0-13-gbb96280,12.1.rc1,12.1.rc1+1,12.1.rc1+2,12.1.rc1+5,12.1.rc1+8,12.1.rc1-1-g06d7636+1,12.1.rc1-1-g253890b+5,12.1.rc1-1-g3d31b68+7,12.1.rc1-1-g3db6b75+1,12.1.rc1-1-g5c1385a+3,12.1.rc1-1-g83b2247,12.1.rc1-1-g90cb4cf+6,12.1.rc1-1-g91da24b+3,12.1.rc1-2-g3521f8a,12.1.rc1-2-g39433dd+4,12.1.rc1-2-g486411b+2,12.1.rc1-2-g4c2be76,12.1.rc1-2-gc9c0491,12.1.rc1-2-gda2cd4f+6,12.1.rc1-3-g3391c73+2,12.1.rc1-3-g8c1bd6c+1,12.1.rc1-3-gcf4b6cb+2,12.1.rc1-4-g057223e+1,12.1.rc1-4-g19ed13b+2,12.1.rc1-4-g30492a7
LSSTDataManagementBasePackage
posixStorage.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 standard_library
25 standard_library.install_aliases()
26 from past.builtins import basestring
27 import copy
28 import pickle
29 import importlib
30 import os
31 import urllib.parse
32 
33 import yaml
34 
35 from . import LogicalLocation, Persistence, Policy, StorageList, Registry, Storage, RepositoryCfg, safeFileIo
36 from lsst.log import Log
37 import lsst.pex.policy as pexPolicy
38 from .safeFileIo import SafeFilename
39 
40 
42 
43  def __init__(self, uri):
44  """Initializer
45 
46  :return:
47  """
48  self.log = Log.getLogger("daf.persistence.butler")
49  self.root = parseRes = urllib.parse.urlparse(uri).path
50  if self.root and not os.path.exists(self.root):
51  os.makedirs(self.root)
52 
53  # Always use an empty Persistence policy until we can get rid of it
54  persistencePolicy = pexPolicy.Policy()
55  self.persistence = Persistence.getPersistence(persistencePolicy)
56 
57  self.registry = Registry.create(location=self.root)
58 
59  def __repr__(self):
60  return 'PosixStorage(root=%s)' % self.root
61 
62  @staticmethod
64  """Get a persisted RepositoryCfg
65  """
66  repositoryCfg = None
67  parseRes = urllib.parse.urlparse(uri)
68  loc = os.path.join(parseRes.path, 'repositoryCfg.yaml')
69  if os.path.exists(loc):
70  with open(loc, 'r') as f:
71  repositoryCfg = yaml.load(f)
72  if repositoryCfg.root is None:
73  repositoryCfg.root = parseRes.path
74  return repositoryCfg
75 
76  @staticmethod
77  def getRepositoryCfg(uri):
78  repositoryCfg = PosixStorage._getRepositoryCfg(uri)
79  if repositoryCfg is not None:
80  return repositoryCfg
81 
82  # if no repository cfg, is it a legacy repository?
83  parseRes = urllib.parse.urlparse(uri)
84  if repositoryCfg is None:
85  mapper = PosixStorage.getMapperClass(parseRes.path)
86  if mapper is not None:
87  repositoryCfg = RepositoryCfg(mapper=mapper,
88  root=parseRes.path,
89  mapperArgs=None,
90  parents=None,
91  isLegacyRepository=True)
92  return repositoryCfg
93 
94  @staticmethod
95  def putRepositoryCfg(cfg, loc=None):
96  if cfg.isLegacyRepository:
97  # don't write cfgs to legacy repositories; they take care of themselves in other ways (e.g. by
98  # the _parent symlink)
99  return
100  if loc is None or cfg.root == loc:
101  # the cfg is at the root location of the repository so don't write root, let it be implicit in the
102  # location of the cfg.
103  cfg = copy.copy(cfg)
104  loc = cfg.root
105  cfg.root = None
106  if not os.path.exists(loc):
107  os.makedirs(loc)
108  loc = os.path.join(loc, 'repositoryCfg.yaml')
109  with safeFileIo.FileForWriteOnceCompareSame(loc) as f:
110  yaml.dump(cfg, f)
111 
112  @staticmethod
113  def getMapperClass(root):
114  """Get the mapper class associated with a repository root.
115 
116  Supports the legacy _parent symlink search (which was only ever posix-only. This should not be used by
117  new code and repositories; they should use the Repository parentCfg mechanism.
118 
119  :param root: the location of a persisted ReositoryCfg is (new style repos), or the location where a
120  _mapper file is (old style repos).
121  :return: a class object or a class instance, depending on the state of the mapper when the repository
122  was created.
123  """
124  if not (root):
125  return None
126 
127  cfg = PosixStorage._getRepositoryCfg(root)
128  if cfg is not None:
129  return cfg.mapper
130 
131  # Find a "_mapper" file containing the mapper class name
132  basePath = root
133  mapperFile = "_mapper"
134  while not os.path.exists(os.path.join(basePath, mapperFile)):
135  # Break abstraction by following _parent links from CameraMapper
136  if os.path.exists(os.path.join(basePath, "_parent")):
137  basePath = os.path.join(basePath, "_parent")
138  else:
139  mapperFile = None
140  break
141 
142  if mapperFile is not None:
143  mapperFile = os.path.join(basePath, mapperFile)
144 
145  # Read the name of the mapper class and instantiate it
146  with open(mapperFile, "r") as f:
147  mapperName = f.readline().strip()
148  components = mapperName.split(".")
149  if len(components) <= 1:
150  raise RuntimeError("Unqualified mapper name %s in %s" %
151  (mapperName, mapperFile))
152  pkg = importlib.import_module(".".join(components[:-1]))
153  return getattr(pkg, components[-1])
154 
155  return None
156 
157  def mapperClass(self):
158  """Get the class object for the mapper specified in the stored repository"""
159  return PosixStorage.getMapperClass(self.root)
160 
161  def write(self, butlerLocation, obj):
162  """Writes an object to a location and persistence format specified by ButlerLocation
163 
164  :param butlerLocation: the location & formatting for the object to be written.
165  :param obj: the object to be written.
166  :return: None
167  """
168  self.log.debug("Put location=%s obj=%s", butlerLocation, obj)
169 
170  additionalData = butlerLocation.getAdditionalData()
171  storageName = butlerLocation.getStorageName()
172  locations = butlerLocation.getLocations()
173 
174  pythonType = butlerLocation.getPythonType()
175  if pythonType is not None:
176  if isinstance(pythonType, basestring):
177  # import this pythonType dynamically
178  pythonTypeTokenList = pythonType.split('.')
179  importClassString = pythonTypeTokenList.pop()
180  importClassString = importClassString.strip()
181  importPackage = ".".join(pythonTypeTokenList)
182  importType = __import__(importPackage, globals(), locals(), [importClassString], 0)
183  pythonType = getattr(importType, importClassString)
184  # todo this effectively defines the butler posix "do serialize" command to be named "put". This has
185  # implications; write now I'm worried that any python type that can be written to disk and has a
186  # method called 'put' will be called here (even if it's e.g. destined for FitsStorage).
187  # We might want a somewhat more specific API.
188  if hasattr(pythonType, 'butlerWrite'):
189  pythonType.butlerWrite(obj, butlerLocation=butlerLocation)
190  return
191 
192  with SafeFilename(locations[0]) as locationString:
193  logLoc = LogicalLocation(locationString, additionalData)
194 
195  if storageName == "PickleStorage":
196  with open(logLoc.locString(), "wb") as outfile:
197  pickle.dump(obj, outfile, pickle.HIGHEST_PROTOCOL)
198  return
199 
200  if storageName == "ConfigStorage":
201  obj.save(logLoc.locString())
202  return
203 
204  if storageName == "FitsCatalogStorage":
205  flags = additionalData.getInt("flags", 0)
206  obj.writeFits(logLoc.locString(), flags=flags)
207  return
208 
209  # Create a list of Storages for the item.
210  storageList = StorageList()
211  storage = self.persistence.getPersistStorage(storageName, logLoc)
212  storageList.append(storage)
213 
214  if storageName == 'FitsStorage':
215  self.persistence.persist(obj, storageList, additionalData)
216  return
217 
218  # Persist the item.
219  if hasattr(obj, '__deref__'):
220  # We have a smart pointer, so dereference it.
221  self.persistence.persist(obj.__deref__(), storageList, additionalData)
222  else:
223  self.persistence.persist(obj, storageList, additionalData)
224 
225  def read(self, butlerLocation):
226  """Read from a butlerLocation.
227 
228  :param butlerLocation:
229  :return: a list of objects as described by the butler location. One item for each location in
230  butlerLocation.getLocations()
231  """
232  additionalData = butlerLocation.getAdditionalData()
233  # Create a list of Storages for the item.
234  storageName = butlerLocation.getStorageName()
235  results = []
236  locations = butlerLocation.getLocations()
237  pythonType = butlerLocation.getPythonType()
238  if pythonType is not None:
239  if isinstance(pythonType, basestring):
240  # import this pythonType dynamically
241  pythonTypeTokenList = pythonType.split('.')
242  importClassString = pythonTypeTokenList.pop()
243  importClassString = importClassString.strip()
244  importPackage = ".".join(pythonTypeTokenList)
245  importType = __import__(importPackage, globals(), locals(), [importClassString], 0)
246  pythonType = getattr(importType, importClassString)
247 
248  # see note re. discomfort with the name 'butlerWrite' in the write method, above.
249  # Same applies to butlerRead.
250  if hasattr(pythonType, 'butlerRead'):
251  results = pythonType.butlerRead(butlerLocation=butlerLocation)
252  return results
253 
254  for locationString in locations:
255  logLoc = LogicalLocation(locationString, additionalData)
256 
257  if storageName == "PafStorage":
258  finalItem = pexPolicy.Policy.createPolicy(logLoc.locString())
259  elif storageName == "YamlStorage":
260  finalItem = Policy(filePath=logLoc.locString())
261  elif storageName == "PickleStorage":
262  if not os.path.exists(logLoc.locString()):
263  raise RuntimeError("No such pickle file: " + logLoc.locString())
264  with open(logLoc.locString(), "rb") as infile:
265  finalItem = pickle.load(infile)
266  elif storageName == "FitsCatalogStorage":
267  if not os.path.exists(logLoc.locString()):
268  raise RuntimeError("No such FITS catalog file: " + logLoc.locString())
269  hdu = additionalData.getInt("hdu", 0)
270  flags = additionalData.getInt("flags", 0)
271  finalItem = pythonType.readFits(logLoc.locString(), hdu, flags)
272  elif storageName == "ConfigStorage":
273  if not os.path.exists(logLoc.locString()):
274  raise RuntimeError("No such config file: " + logLoc.locString())
275  finalItem = pythonType()
276  finalItem.load(logLoc.locString())
277  else:
278  storageList = StorageList()
279  storage = self.persistence.getRetrieveStorage(storageName, logLoc)
280  storageList.append(storage)
281  itemData = self.persistence.unsafeRetrieve(
282  butlerLocation.getCppType(), storageList, additionalData)
283  finalItem = pythonType.swigConvert(itemData)
284  results.append(finalItem)
285 
286  return results
287 
288  def exists(self, location):
289  """Check if 'location' exists relative to root.
290 
291  :param location:
292  :return:
293  """
294  return os.path.exists(os.path.join(self.root, location))
295 
296  def locationWithRoot(self, location):
297  """Get the full path to the location.
298 
299  :param location:
300  :return:
301  """
302  return os.path.join(self.root, location)
303 
304  def lookup(self, *args, **kwargs):
305  """Perform a lookup in the registry"""
306  return self.registry.lookup(*args, **kwargs)
307 
308 
309 Storage.registerStorageClass(scheme='', cls=PosixStorage)
310 Storage.registerStorageClass(scheme='file', cls=PosixStorage)
a container for holding hierarchical configuration data in memory.
Definition: Policy.h:169
Definition: Log.h:716
Abstract base class for storage implementations.
Definition: Storage.h:60