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
policy.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 #
4 # LSST Data Management System
5 # Copyright 2015 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 builtins import str
25 from past.builtins import basestring
26 from future.utils import with_metaclass
27 
28 import collections
29 import collections.abc
30 import copy
31 import os
32 import sys
33 import warnings
34 import yaml
35 
36 import lsst.pex.policy as pexPolicy
37 import lsst.utils
38 
39 from yaml.representer import Representer
40 yaml.add_representer(collections.defaultdict, Representer.represent_dict)
41 
42 # UserDict and yaml have defined metaclasses and Python 3 does not allow multiple
43 # inheritance of classes with distinct metaclasses. We therefore have to
44 # create a new baseclass that Policy can inherit from. This is because the metaclass
45 # syntax differs between versions
46 
47 if sys.version_info[0] >= 3:
48  class _PolicyMeta(type(collections.UserDict), type(yaml.YAMLObject)):
49  pass
50 
51  class _PolicyBase(with_metaclass(_PolicyMeta, collections.UserDict, yaml.YAMLObject)):
52  pass
53 else:
54  class _PolicyBase(collections.UserDict, yaml.YAMLObject):
55  pass
56 
57 
59  """Policy implements a datatype that is used by Butler for configuration parameters.
60  It is essentially a dict with key/value pairs, including nested dicts (as values). In fact, it can be
61  initialized with a dict. The only caveat is that keys may NOT contain dots ('.'). This is explained next:
62  Policy extends the dict api so that hierarchical values may be accessed with dot-delimited notiation.
63  That is, foo.getValue('a.b.c') is the same as foo['a']['b']['c'] is the same as foo['a.b.c'], and either
64  of these syntaxes may be used.
65 
66  Storage formats supported:
67  - yaml: read and write is supported.
68  - pex policy: read is supported, although this is deprecated and will at some point be removed.
69  """
70 
71  def __init__(self, other=None):
72  """Initialize the Policy. Other can be used to initialize the Policy in a variety of ways:
73  other (string) Treated as a path to a policy file on disk. Must end with '.paf' or '.yaml'.
74  other (Pex Policy) Initializes this Policy with the values in the passed-in Pex Policy.
75  other (Policy) Copies the other Policy's values into this one.
76  other (dict) Copies the values from the dict into this Policy.
77  """
78  collections.UserDict.__init__(self)
79 
80  if other is None:
81  return
82 
83  if isinstance(other, collections.abc.Mapping):
84  self.update(other)
85  elif isinstance(other, Policy):
86  self.data = copy.deepcopy(other.data)
87  elif isinstance(other, basestring):
88  # if other is a string, assume it is a file path.
89  self.__initFromFile(other)
90  elif isinstance(other, pexPolicy.Policy):
91  # if other is an instance of a Pex Policy, load it accordingly.
92  self.__initFromPexPolicy(other)
93  else:
94  # if the policy specified by other could not be loaded raise a runtime error.
95  raise RuntimeError("A Policy could not be loaded from other:%s" % other)
96 
97  def ppprint(self):
98  """helper function for debugging, prints a policy out in a readable way in the debugger.
99 
100  use: pdb> print myPolicyObject.pprint()
101  :return: a prettyprint formatted string representing the policy
102  """
103  import pprint
104  return pprint.pformat(self.data, indent=2, width=1)
105 
106  def __repr__(self):
107  return self.data.__repr__()
108 
109  def __initFromFile(self, path):
110  """Load a file from path. If path is a list, will pick one to use, according to order specified
111  by extensionPreference.
112 
113  :param path: string or list of strings, to a persisted policy file.
114  :param extensionPreference: the order in which to try to open files. Will use the first one that
115  succeeds.
116  :return:
117  """
118  policy = None
119  if path.endswith('yaml'):
120  self.__initFromYamlFile(path)
121  elif path.endswith('paf'):
122  policy = pexPolicy.Policy.createPolicy(path)
123  self.__initFromPexPolicy(policy)
124  else:
125  raise RuntimeError("Unhandled policy file type:%s" % path)
126 
127  def __initFromPexPolicy(self, pexPolicy):
128  """Load values from a pex policy.
129 
130  :param pexPolicy:
131  :return:
132  """
133  names = pexPolicy.names()
134  names.sort()
135  for name in names:
136  if pexPolicy.getValueType(name) == pexPolicy.POLICY:
137  if name in self:
138  continue
139  else:
140  self[name] = {}
141  else:
142  if pexPolicy.isArray(name):
143  self[name] = pexPolicy.getArray(name)
144  else:
145  self[name] = pexPolicy.get(name)
146  return self
147 
148  def __initFromYamlFile(self, path):
149  """Opens a file at a given path and attempts to load it in from yaml.
150 
151  :param path:
152  :return:
153  """
154  with open(path, 'r') as f:
155  self.__initFromYaml(f)
156 
157  def __initFromYaml(self, stream):
158  """Loads a YAML policy from any readable stream that contains one.
159 
160  :param stream:
161  :return:
162  """
163  # will raise yaml.YAMLError if there is an error loading the file.
164  self.data = yaml.load(stream)
165  return self
166 
167  def __getitem__(self, name):
168  data = self.data
169  for key in name.split('.'):
170  if data is None:
171  return None
172  if key in data:
173  data = data[key]
174  else:
175  return None
176  if isinstance(data, collections.abc.Mapping):
177  data = Policy(data)
178  return data
179 
180  def __setitem__(self, name, value):
181  if isinstance(value, collections.abc.Mapping):
182  keys = name.split('.')
183  d = {}
184  cur = d
185  for key in keys[0:-1]:
186  cur[key] = {}
187  cur = cur[key]
188  cur[keys[-1]] = value
189  self.update(d)
190  data = self.data
191  keys = name.split('.')
192  for key in keys[0:-1]:
193  data = data.setdefault(key, {})
194  data[keys[-1]] = value
195 
196  def __contains__(self, key):
197  d = self.data
198  keys = key.split('.')
199  for k in keys[0:-1]:
200  if k in d:
201  d = d[k]
202  else:
203  return False
204  return keys[-1] in d
205 
206  @staticmethod
207  def defaultPolicyFile(productName, fileName, relativePath=None):
208  """Get the path to a default policy file.
209 
210  Determines a directory for the product specified by productName. Then Concatenates
211  productDir/relativePath/fileName (or productDir/fileName if relativePath is None) to find the path
212  to the default Policy file
213 
214  @param productName (string) The name of the product that the default policy is installed as part of
215  @param fileName (string) The name of the policy file. Can also include a path to the file relative to
216  the directory where the product is installed.
217  @param relativePath (string) The relative path from the directior where the product is installed to
218  the location where the file (or the path to the file) is found. If None
219  (default), the fileName argument is relative to the installation
220  directory.
221  """
222  basePath = lsst.utils.getPackageDir(productName)
223  if not basePath:
224  raise RuntimeError("No product installed for productName: %s" % basePath)
225  if relativePath is not None:
226  basePath = os.path.join(basePath, relativePath)
227  fullFilePath = os.path.join(basePath, fileName)
228  return fullFilePath
229 
230  def update(self, other):
231  """Like dict.update, but will add or modify keys in nested dicts, instead of overwriting the nested
232  dict entirely.
233 
234  For example, for the given code:
235  foo = {'a': {'b': 1}}
236  foo.update({'a': {'c': 2}})
237 
238  If foo is a dict, then after the update foo == {'a': {'c': 2}}
239  But if foo is a Policy, then after the update foo == {'a': {'b': 1, 'c': 2}}
240  """
241  def doUpdate(d, u):
242  for k, v in u.items():
243  if isinstance(d, collections.abc.Mapping):
244  if isinstance(v, collections.abc.Mapping):
245  r = doUpdate(d.get(k, {}), v)
246  d[k] = r
247  else:
248  d[k] = u[k]
249  else:
250  d = {k: u[k]}
251  return d
252  doUpdate(self.data, other)
253 
254  def merge(self, other):
255  """Like Policy.update, but will add keys & values from other that DO NOT EXIST in self. Keys and
256  values that already exist in self will NOT be overwritten.
257 
258  :param other:
259  :return:
260  """
261  otherCopy = copy.deepcopy(other)
262  otherCopy.update(self)
263  self.data = otherCopy.data
264 
265  def names(self, topLevelOnly=False):
266  """Get the dot-delimited name of all the keys in the hierarchy.
267  NOTE: this is different than the built-in method dict.keys, which will return only the first level
268  keys.
269  """
270  if topLevelOnly:
271  return list(self.keys())
272 
273  def getKeys(d, keys, base):
274  for key in d:
275  val = d[key]
276  levelKey = base + '.' + key if base is not None else key
277  keys.append(levelKey)
278  if isinstance(val, collections.abc.Mapping):
279  getKeys(val, keys, levelKey)
280  keys = []
281  getKeys(self.data, keys, None)
282  return keys
283 
284  def asArray(self, name):
285  """Get a value as an array. May contain one or more elements.
286 
287  :param key:
288  :return:
289  """
290  val = self.get(name)
291  if isinstance(val, basestring):
292  val = [val]
293  elif not isinstance(val, collections.abc.Container):
294  val = [val]
295  return val
296 
297  # Deprecated methods that mimic pex_policy api.
298  # These are supported (for now), but callers should use the dict api.
299 
300  def getValue(self, name):
301  """Get the value for a parameter name/key. See class notes about dot-delimited access.
302 
303  :param name:
304  :return: the value for the given name.
305  """
306  warnings.warn_explicit("Deprecated. Use []", DeprecationWarning)
307  return self[name]
308 
309  def setValue(self, name, value):
310  """Set the value for a parameter name/key. See class notes about dot-delimited access.
311 
312  :param name:
313  :return: None
314  """
315  warnings.warn("Deprecated. Use []", DeprecationWarning)
316  self[name] = value
317 
318  def mergeDefaults(self, other):
319  """For any keys in other that are not present in self, sets that key and its value into self.
320 
321  :param other: another Policy
322  :return: None
323  """
324  warnings.warn("Deprecated. Use .merge()", DeprecationWarning)
325  self.merge(other)
326 
327  def exists(self, key):
328  """Query if a key exists in this Policy
329 
330  :param key:
331  :return: True if the key exists, else false.
332  """
333  warnings.warn("Deprecated. Use 'key in object'", DeprecationWarning)
334  return key in self
335 
336  def getString(self, key):
337  """Get the string value of a key.
338 
339  :param key:
340  :return: the value for key
341  """
342  warnings.warn("Deprecated. Use []", DeprecationWarning)
343  return str(self[key])
344 
345  def getBool(self, key):
346  """Get the value of a key.
347 
348  :param key:
349  :return: the value for key
350  """
351  warnings.warn("Deprecated. Use []", DeprecationWarning)
352  return bool(self[key])
353 
354  def getPolicy(self, key):
355  """Get a subpolicy.
356 
357  :param key:
358  :return:
359  """
360  warnings.warn("Deprecated. Use []", DeprecationWarning)
361  return self[key]
362 
363  def getStringArray(self, key):
364  """Get a value as an array. May contain one or more elements.
365 
366  :param key:
367  :return:
368  """
369  warnings.warn("Deprecated. Use asArray()", DeprecationWarning)
370  val = self.get(key)
371  if isinstance(val, basestring):
372  val = [val]
373  elif not isinstance(val, collections.abc.Container):
374  val = [val]
375  return val
376 
377  def __lt__(self, other):
378  if isinstance(other, Policy):
379  other = other.data
380  return self.data < other
381 
382  def __le__(self, other):
383  if isinstance(other, Policy):
384  other = other.data
385  return self.data <= other
386 
387  def __eq__(self, other):
388  if isinstance(other, Policy):
389  other = other.data
390  return self.data == other
391 
392  def __ne__(self, other):
393  if isinstance(other, Policy):
394  other = other.data
395  return self.data != other
396 
397  def __gt__(self, other):
398  if isinstance(other, Policy):
399  other = other.data
400  return self.data > other
401 
402  def __ge__(self, other):
403  if isinstance(other, Policy):
404  other = other.data
405  return self.data >= other
406 
407 
409 
410  def dump(self, output):
411  """Writes the policy to a yaml stream.
412 
413  :param stream:
414  :return:
415  """
416  # First a set of known keys is handled and written to the stream in a specific order for readability.
417  # After the expected/ordered keys are weritten to the stream the remainder of the keys are written to
418  # the stream.
419  data = copy.copy(self.data)
420  keys = ['defects', 'needCalibRegistry', 'levels', 'defaultLevel', 'defaultSubLevels', 'camera',
421  'exposures', 'calibrations', 'datasets']
422  for key in keys:
423  try:
424  yaml.safe_dump({key: data.pop(key)}, output, default_flow_style=False)
425  output.write('\n')
426  except KeyError:
427  pass
428  if data:
429  yaml.safe_dump(data, output, default_flow_style=False)
430 
431  def dumpToFile(self, path):
432  """Writes the policy to a file.
433 
434  :param path:
435  :return:
436  """
437  with open(path, 'w') as f:
438  self.dump(f)
def __initFromYaml(self, stream)
Definition: policy.py:157
def __initFromYamlFile(self, path)
Definition: policy.py:148
def dump(self, output)
i/o #
Definition: policy.py:410
def __initFromPexPolicy(self, pexPolicy)
Definition: policy.py:127
a container for holding hierarchical configuration data in memory.
Definition: Policy.h:169
def __setitem__(self, name, value)
Definition: policy.py:180
std::string getPackageDir(std::string const &packageName)
return the root directory of a setup package
Definition: packaging.cc:33
table::Key< int > type
Definition: Detector.cc:164
def __init__(self, other=None)
Definition: policy.py:71
def defaultPolicyFile(productName, fileName, relativePath=None)
Definition: policy.py:207
def mergeDefaults(self, other)
Definition: policy.py:318
def names(self, topLevelOnly=False)
Definition: policy.py:265
daf::base::PropertyList * list
Definition: fits.cc:833
def setValue(self, name, value)
Definition: policy.py:309