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