LSSTApplications  17.0+124,17.0+14,17.0+73,18.0.0+37,18.0.0+80,18.0.0-4-g68ffd23+4,18.1.0-1-g0001055+12,18.1.0-1-g03d53ef+5,18.1.0-1-g1349e88+55,18.1.0-1-g2505f39+44,18.1.0-1-g5315e5e+4,18.1.0-1-g5e4b7ea+14,18.1.0-1-g7e8fceb+4,18.1.0-1-g85f8cd4+48,18.1.0-1-g8ff0b9f+4,18.1.0-1-ga2c679d+1,18.1.0-1-gd55f500+35,18.1.0-10-gb58edde+2,18.1.0-11-g0997b02+4,18.1.0-13-gfe4edf0b+12,18.1.0-14-g259bd21+21,18.1.0-19-gdb69f3f+2,18.1.0-2-g5f9922c+24,18.1.0-2-gd3b74e5+11,18.1.0-2-gfbf3545+32,18.1.0-26-g728bddb4+5,18.1.0-27-g6ff7ca9+2,18.1.0-3-g52aa583+25,18.1.0-3-g8ea57af+9,18.1.0-3-gb69f684+42,18.1.0-3-gfcaddf3+6,18.1.0-32-gd8786685a,18.1.0-4-gf3f9b77+6,18.1.0-5-g1dd662b+2,18.1.0-5-g6dbcb01+41,18.1.0-6-gae77429+3,18.1.0-7-g9d75d83+9,18.1.0-7-gae09a6d+30,18.1.0-9-gc381ef5+4,w.2019.45
LSSTDataManagementBasePackage
repositoryCfg.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 
25 # -*- python -*-
26 
27 import copy
28 import yaml
29 from . import iterify, doImport, Storage, ParentsMismatch
30 from past.builtins import basestring
31 
32 
33 class RepositoryCfg(yaml.YAMLObject):
34  """RepositoryCfg stores the configuration of a repository. Its contents are persisted to the repository
35  when the repository is created in persistent storage. Thereafter the the RepositoryCfg should not change.
36 
37  Parameters
38  ----------
39  mapper : string
40  The mapper associated with the repository. The string should be importable to a class object.
41  mapperArgs : dict
42  Arguments & values to pass to the mapper when initializing it.
43  parents : list of URI
44  URIs to the locaiton of the parent RepositoryCfgs of this repository.
45  policy : dict
46  Policy associated with this repository, overrides all other policy data (which may be loaded from
47  policies in derived packages).
48  deserializing : bool
49  Butler internal use only. This flag is used to indicate to the init funciton that the repository class
50  is being deserialized and should not perform certain operations that normally happen in other uses of
51  init.
52  """
53  yaml_tag = u"!RepositoryCfg_v1"
54 
55  def __init__(self, root, mapper, mapperArgs, parents, policy):
56  self._root = root
57  self._mapper = mapper
58  self._mapperArgs = {} if mapperArgs is None else mapperArgs
59  self._parents = []
60  self.addParents(iterify(parents))
61  self._policy = policy
62  self.dirty = True # if dirty, the parameters have been changed since the cfg was read or written.
63 
64  @staticmethod
65  def v1Constructor(loader, node):
66  """Constructor for 'version 1' of the serlized RepositoryCfg.
67 
68  If new parameters are added to RepositoryCfg they will have to be checked for in d; if they are there
69  then their value should be used and if they are not there a default value must be used in place.
70 
71  In case the structure of the serialzed file must be changed in a way that invalidates some of the
72  keys:
73  1. Increment the version number (after _v1) in the yaml_tag of this class.
74  2. Add a new constructor (similar to this one) to deserialze new serializations of this class.
75  3. Registered the new constructor for the new version with yaml, the same way it is done at the bottom
76  of this file.
77  4. All constructors for the older version(s) of persisted RepositoryCfg must be changed to adapt
78  the old keys to their new uses and create the current (new) version of a repository cfg, or raise a
79  RuntimeError in the case that older versions of serialized RepositoryCfgs can not be adapted.
80  There is an example of migrating from a fictitious v0 to v1 in tests/repositoryCfg.py
81  """
82  d = loader.construct_mapping(node)
83  cfg = RepositoryCfg(root=d['_root'], mapper=d['_mapper'], mapperArgs=d['_mapperArgs'],
84  parents=[], policy=d.get('_policy', None))
85  # Where possible we mangle the parents so that they are relative to root, for example if the root and
86  # the parents are both in the same PosixStorage. The parents are serialized in mangled form; when
87  # deserializing the parents we do not re-mangle them.
88  cfg._parents = d['_parents']
89  if cfg._parents is None:
90  cfg._parents = []
91  cfg.dirty = False
92  return cfg
93 
94  def __eq__(self, other):
95  if not other:
96  return False
97  return self.root == other.root and \
98  self.mapper == other.mapper and \
99  self.mapperArgs == other.mapperArgs and \
100  self.parents == other.parents and \
101  self.policy == other.policy
102 
103  def __ne__(self, other):
104  return not self.__eq__(other)
105 
106  def extend(self, other):
107  """Extend this RepositoryCfg with extendable values from the other RepositoryCfg.
108 
109  Currently the only extendable value is parents; see `extendParents` for more detials about extending
110  the parents list.
111 
112  Parameters
113  ----------
114  other : RepositoryCfg
115  A RepositoryCfg instance to update values from.
116 
117  Raises
118  ------
119  RuntimeError
120  If non-extendable parameters do not match a RuntimeError will be raised.
121  (If this RepositoryCfg's parents can not be extended with the parents of the other repository,
122  extendParents will raise).
123  """
124  if (self.root != other.root or
125  self.mapper != other.mapper or
126  self.mapperArgs != other.mapperArgs or
127  self.policy != other.policy):
128  raise RuntimeError("{} can not be extended with cfg:{}".format(self, other))
129  self.extendParents(other.parents)
130 
131  def _extendsParents(self, newParents):
132  """Query if a list of parents starts with the same list of parents as this RepositoryCfg's parents,
133  with new parents at the end.
134 
135  Parameters
136  ----------
137  newParents : list of string and/or RepositoryCfg
138  A list of parents that contains all the parents that would be in this RepositoryCfg.
139  This must include parents that may already be in this RepositoryCfg's parents list. Paths must be
140  in absolute form (not relative).
141 
142  Returns
143  -------
144  bool
145  True if the beginning of the new list matches this RepositoryCfg's parents list, False if not.
146  """
147  doesExtendParents = False
148  return doesExtendParents
149 
150  def extendParents(self, newParents):
151  """Determine if a parents list matches our parents list, with extra items at the end. If a list of
152  parents does not match but the mismatch is because of new parents at the end of the list, then they
153  can be added to the cfg.
154 
155  Parameters
156  ----------
157  newParents : list of string
158  A list of parents that contains all the parents that are to be recorded into this RepositoryCfg.
159  This must include parents that may already be in this RepositoryCfg's parents list
160 
161  Raises
162  ------
163  ParentsListMismatch
164  Description
165  """
166  newParents = self._normalizeParents(self.root, newParents)
167  doRaise = False
168  if self._parents != newParents:
169  if all(x == y for (x, y) in zip(self._parents, newParents)):
170  if len(self._parents) < len(newParents):
171  self._parents = newParents
172  self.dirty = True
173  elif len(self._parents) == len(newParents):
174  pass
175  else:
176  doRaise = True
177  else:
178  doRaise = True
179  if doRaise:
180  raise ParentsMismatch(("The beginning of the passed-in parents list: {} does not match the " +
181  "existing parents list in this RepositoryCfg: {}").format(
182  newParents, self._parents))
183 
184  @property
185  def root(self):
186  return self._root
187 
188  @root.setter
189  def root(self, root):
190  if root is not None and self._root is not None:
191  raise RuntimeError("Explicity clear root (set to None) before changing the value of root.")
192  self._root = root
193 
194  @property
195  def mapper(self):
196  return self._mapper
197 
198  @mapper.setter
199  def mapper(self, mapper):
200  if self._mapper is not None:
201  raise RuntimeError("Should not set mapper over previous not-None value.")
202  self.dirty = True
203  self._mapper = mapper
204 
205  @property
206  def mapperArgs(self):
207  return self._mapperArgs
208 
209  @mapperArgs.setter
210  def mapperArgs(self, newDict):
211  self.dirty = True
212  self._mapperArgs = newDict
213 
214  @property
215  def parents(self):
216  return self._denormalizeParents(self.root, self._parents)
217 
218  @staticmethod
219  def _normalizeParents(root, newParents):
220  """Eliminate symlinks in newParents and get the relative path (if one exists) from root to each parent
221  root.
222 
223  Parameters
224  ----------
225  newParents : list containing strings and RepoistoryCfg instances
226  Same as in `addParents`.
227 
228  Returns
229  -------
230  list of strings and RepositoryCfg instances.
231  Normalized list of parents
232  """
233  newParents = iterify(newParents)
234  for i in range(len(newParents)):
235  if isinstance(newParents[i], RepositoryCfg):
236  newParents[i] = copy.copy(newParents[i])
237  parentRoot = newParents[i].root
238  newParents[i].root = None
239  newParents[i].root = Storage.relativePath(root, parentRoot)
240  else:
241  newParents[i] = Storage.relativePath(root, newParents[i])
242  return newParents
243 
244  @staticmethod
245  def _denormalizeParents(root, parents):
246  def getAbs(root, parent):
247  if isinstance(parent, RepositoryCfg):
248  parentRoot = parent.root
249  parent.root = None
250  parent.root = Storage.absolutePath(root, parentRoot)
251  else:
252  parent = Storage.absolutePath(root, parent)
253  return parent
254  return [getAbs(root, parent) for parent in parents]
255 
256  def addParents(self, newParents):
257  """Add a parent or list of parents to this RepositoryCfg
258 
259  Parameters
260  ----------
261  newParents : string or RepositoryCfg instance, or list of these.
262  If string, newParents should be a path or URI to the parent
263  repository. If RepositoryCfg, newParents should be a RepositoryCfg
264  that describes the parent repository in part or whole.
265  """
266  newParents = self._normalizeParents(self.root, newParents)
267  for newParent in newParents:
268  if newParent not in self._parents:
269  self.dirty = True
270  self._parents.append(newParent)
271 
272  @property
273  def policy(self):
274  return self._policy
275 
276  @staticmethod
277  def makeFromArgs(repositoryArgs):
278  cfg = RepositoryCfg(root=repositoryArgs.root,
279  mapper=repositoryArgs.mapper,
280  mapperArgs=repositoryArgs.mapperArgs,
281  parents=None,
282  policy=repositoryArgs.policy)
283  return cfg
284 
285  def matchesArgs(self, repositoryArgs):
286  """Checks that a repositoryArgs instance will work with this repositoryCfg. This is useful
287  when loading an already-existing repository that has a persisted cfg, to ensure that the args that are
288  passed into butler do not conflict with the persisted cfg."""
289  if repositoryArgs.root is not None and self._root != repositoryArgs.root:
290  return False
291 
292  repoArgsMapper = repositoryArgs.mapper
293  cfgMapper = self._mapper
294  if isinstance(repoArgsMapper, basestring):
295  repoArgsMapper = doImport(repoArgsMapper)
296  if isinstance(cfgMapper, basestring):
297  cfgMapper = doImport(cfgMapper)
298  if repoArgsMapper is not None and repoArgsMapper != cfgMapper:
299  return False
300  # check mapperArgs for any keys in common and if their value does not match then return false.
301  if self._mapperArgs is not None and repositoryArgs.mapperArgs is not None:
302  for key in set(self._mapperArgs.keys()) & set(repositoryArgs.mapperArgs):
303  if self._mapperArgs[key] != repositoryArgs.mapperArgs[key]:
304  return False
305  if repositoryArgs.policy and repositoryArgs.policy != self._policy:
306  return False
307 
308  return True
309 
310  def __repr__(self):
311  return "%s(root=%r, mapper=%r, mapperArgs=%r, parents=%s, policy=%s)" % (
312  self.__class__.__name__,
313  self._root,
314  self._mapper,
315  self._mapperArgs,
316  self._parents,
317  self._policy)
318 
319 
320 loaderList = [yaml.Loader, ]
321 try:
322  loaderList.append(yaml.FullLoader)
323 except AttributeError:
324  pass
325 try:
326  loaderList.append(yaml.UnsafeLoader)
327 except AttributeError:
328  pass
329 
330 for loader in loaderList:
331  yaml.add_constructor(u"!RepositoryCfg_v1", RepositoryCfg.v1Constructor, Loader=loader)
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174
def __init__(self, root, mapper, mapperArgs, parents, policy)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
daf::base::PropertySet * set
Definition: fits.cc:902
bool all(CoordinateExpr< N > const &expr) noexcept
Return true if all elements are true.
def doImport(pythonType)
Definition: utils.py:106