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
repository.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 past.builtins import basestring
25 from builtins import object
26 
27 import copy
28 import inspect
29 
30 from lsst.daf.persistence import Storage, listify
31 
32 
33 class RepositoryArgs(object):
34 
35  def __init__(self, root=None, cfgRoot=None, mapper=None, mapperArgs=None, tags=None,
36  mode=None):
37  self.root = root
38  self._cfgRoot = cfgRoot
39  self.mapper = mapper
40  self.mapperArgs = mapperArgs
41  self.tags = set(listify(tags))
42  self.mode = mode
43  self.isLegacyRepository = False
44 
45  def __repr__(self):
46  return "%s(root=%r, cfgRoot=%r, mapper=%r, mapperArgs=%r, tags=%s, mode=%r)" % (
47  self.__class__.__name__, self.root, self._cfgRoot, self.mapper, self.mapperArgs, self.tags,
48  self.mode)
49 
50  @property
51  def cfgRoot(self):
52  if self._cfgRoot is not None:
53  return self._cfgRoot
54  else:
55  return self.root
56 
57  @staticmethod
58  def inputRepo(storage, tags=None):
59  return RepositoryArgs(storage, tags)
60 
61  @staticmethod
62  def outputRepo(storage, mapper=None, mapperArgs=None, tags=None, mode=None):
63  return RepositoryArgs(storage, mapper, mapperArgs, tags, mode)
64 
65  def tag(self, tag):
66  """add a tag to the repository cfg"""
67  if isinstance(tag, basestring):
68  self.tags.add(tag)
69  else:
70  try:
71  self.tags.update(tag)
72  except TypeError:
73  self.tags.add(tag)
74 
75 
76 class Repository(object):
77  """Represents a repository of persisted data and has methods to access that data.
78  """
79 
80  def __init__(self, repositoryCfg):
81  '''Initialize a Repository with parameters input via config.
82 
83  :param args: an instance of RepositoryArgs
84  :return:
85  '''
86  self._storage = Storage.makeFromURI(repositoryCfg.root)
87  self._mapperArgs = repositoryCfg.mapperArgs # keep for reference in matchesArgs
88  self._initMapper(repositoryCfg)
89 
90  def _initMapper(self, repositoryCfg):
91  '''Initialize and keep the mapper in a member var.
92 
93  :param repositoryCfg:
94  :return:
95  '''
96 
97  # rule: If mapper is:
98  # - an object: use it as the mapper.
99  # - a string: import it and instantiate it with mapperArgs
100  # - a class object: instantiate it with mapperArgs
101  mapper = repositoryCfg.mapper
102 
103  # if mapper is a string, import it:
104  if isinstance(mapper, basestring):
105  mapper = __import__(mapper)
106  # now if mapper is a class type (not instance), instantiate it:
107  if inspect.isclass(mapper):
108  mapperArgs = copy.copy(repositoryCfg.mapperArgs)
109  if mapperArgs is None:
110  mapperArgs = {}
111  # so that root doesn't have to be redundantly passed in cfgs, if root is specified in the
112  # storage and if it is an argument to the mapper, make sure that it's present in mapperArgs.
113  for arg in ('root', 'storage'):
114  if arg not in mapperArgs:
115  mro = inspect.getmro(mapper)
116  if mro[-1] is object:
117  mro = mro[:-1]
118  for c in mro:
119  try:
120  if arg in inspect.getargspec(c.__init__).args:
121  mapperArgs[arg] = self._storage.root
122  break
123  except TypeError:
124  pass
125  mapper = mapper(**mapperArgs)
126 
127  self._mapper = mapper
128 
129  def __repr__(self):
130  return 'config(id=%s, storage=%s, parent=%s, mapper=%s, mapperArgs=%s, cls=%s)' % \
131  (self.id, self._storage, self.parent, self.mapper, self.mapperArgs, self.cls)
132 
133  # todo want a way to make a repository read-only
134  def write(self, butlerLocation, obj):
135  """Write a dataset to Storage.
136 
137  :param butlerLocation: Contains the details needed to find the desired dataset.
138  :param dataset: The dataset to be written.
139  :return:
140  """
141  return self._storage.write(butlerLocation, obj)
142 
143  def read(self, butlerLocation):
144  """Read a dataset from Storage.
145 
146  :param butlerLocation: Contains the details needed to find the desired dataset.
147  :return: An instance of the dataset requested by butlerLocation.
148  """
149  return self._storage.read(butlerLocation)
150 
151  #################
152  # Mapper Access #
153 
154  def mappers(self):
155  return (self._mapper, )
156 
157  def getKeys(self, *args, **kwargs):
158  """
159  Get the keys available in the repository/repositories.
160  :param args:
161  :param kwargs:
162  :return: A dict of {key:valueType}
163  """
164  # todo: getKeys is not in the mapper API
165  if self._mapper is None:
166  return None
167  keys = self._mapper.getKeys(*args, **kwargs)
168  return keys
169 
170  def map(self, *args, **kwargs):
171  """Find a butler location for the given arguments.
172  See mapper.map for more information about args and kwargs.
173 
174  :param args: arguments to be passed on to mapper.map
175  :param kwargs: keyword arguments to be passed on to mapper.map
176  :return: The type of item is dependent on the mapper being used but is typically a ButlerLocation.
177  """
178  if self._mapper is None:
179  return None
180  loc = self._mapper.map(*args, **kwargs)
181  if loc is None:
182  return None
183  loc.setRepository(self)
184  return loc
185 
186  def queryMetadata(self, *args, **kwargs):
187  """Gets possible values for keys given a partial data id.
188 
189  See mapper documentation for more explanation about queryMetadata.
190 
191  :param args: arguments to be passed on to mapper.queryMetadata
192  :param kwargs: keyword arguments to be passed on to mapper.queryMetadata
193  :return:The type of item is dependent on the mapper being used but is typically a set that contains
194  available values for the keys in the format input argument.
195  """
196  if self._mapper is None:
197  return None
198  ret = self._mapper.queryMetadata(*args, **kwargs)
199  return ret
200 
201  def backup(self, *args, **kwargs):
202  """Perform mapper.backup.
203 
204  See mapper.backup for more information about args and kwargs.
205 
206  :param args: arguments to be passed on to mapper.backup
207  :param kwargs: keyword arguments to be passed on to mapper.backup
208  :return: None
209  """
210  if self._mapper is None:
211  return None
212  self._mapper.backup(*args, **kwargs)
213 
215  """Get the default level of the mapper.
216 
217  This is typically used if no level is passed into butler methods that call repository.getKeys and/or
218  repository.queryMetadata. There is a bug in that code because it gets the default level from this
219  repository but then uses that value when searching all repositories. If this and other repositories
220  have dissimilar data, the default level value will be nonsensical. A good example of this issue is in
221  Butler.subset; it needs refactoring.
222 
223  :return:
224  """
225  if self._mapper is None:
226  return None
227  return self._mapper.getDefaultLevel()