25 import collections.abc
34 from yaml.representer
import Representer
35 yaml.add_representer(collections.defaultdict, Representer.represent_dict)
45 class _PolicyBase(collections.UserDict, yaml.YAMLObject, metaclass=_PolicyMeta):
50 """Policy implements a datatype that is used by Butler for configuration parameters.
51 It is essentially a dict with key/value pairs, including nested dicts (as values). In fact, it can be
52 initialized with a dict. The only caveat is that keys may NOT contain dots ('.'). This is explained next:
53 Policy extends the dict api so that hierarchical values may be accessed with dot-delimited notiation.
54 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
55 of these syntaxes may be used.
57 Storage formats supported:
58 - yaml: read and write is supported.
59 - pex policy: read is supported, although this is deprecated and will at some point be removed.
63 """Initialize the Policy. Other can be used to initialize the Policy in a variety of ways:
64 other (string) Treated as a path to a policy file on disk. Must end with '.paf' or '.yaml'.
65 other (Pex Policy) Initializes this Policy with the values in the passed-in Pex Policy.
66 other (Policy) Copies the other Policy's values into this one.
67 other (dict) Copies the values from the dict into this Policy.
69 collections.UserDict.__init__(self)
74 if isinstance(other, collections.abc.Mapping):
76 elif isinstance(other, Policy):
77 self.
data = copy.deepcopy(other.data)
78 elif isinstance(other, str):
86 raise RuntimeError(
"A Policy could not be loaded from other:%s" % other)
89 """helper function for debugging, prints a policy out in a readable way in the debugger.
91 use: pdb> print myPolicyObject.pprint()
92 :return: a prettyprint formatted string representing the policy
95 return pprint.pformat(self.
data, indent=2, width=1)
100 def __initFromFile(self, path):
101 """Load a file from path. If path is a list, will pick one to use, according to order specified
102 by extensionPreference.
104 :param path: string or list of strings, to a persisted policy file.
105 :param extensionPreference: the order in which to try to open files. Will use the first one that
110 if path.endswith(
'yaml'):
112 elif path.endswith(
'paf'):
113 policy = pexPolicy.Policy.createPolicy(path)
116 raise RuntimeError(
"Unhandled policy file type:%s" % path)
118 def __initFromPexPolicy(self, pexPolicy):
119 """Load values from a pex policy.
124 names = pexPolicy.names()
127 if pexPolicy.getValueType(name) == pexPolicy.POLICY:
133 if pexPolicy.isArray(name):
134 self[name] = pexPolicy.getArray(name)
136 self[name] = pexPolicy.get(name)
139 def __initFromYamlFile(self, path):
140 """Opens a file at a given path and attempts to load it in from yaml.
145 with open(path,
'r')
as f:
148 def __initFromYaml(self, stream):
149 """Loads a YAML policy from any readable stream that contains one.
157 loader = yaml.FullLoader
158 except AttributeError:
160 self.
data = yaml.load(stream, Loader=loader)
165 for key
in name.split(
'.'):
172 if isinstance(data, collections.abc.Mapping):
177 if isinstance(value, collections.abc.Mapping):
178 keys = name.split(
'.')
181 for key
in keys[0:-1]:
184 cur[keys[-1]] = value
187 keys = name.split(
'.')
188 for key
in keys[0:-1]:
189 data = data.setdefault(key, {})
190 data[keys[-1]] = value
194 keys = key.split(
'.')
204 """Get the path to a default policy file.
206 Determines a directory for the product specified by productName. Then Concatenates
207 productDir/relativePath/fileName (or productDir/fileName if relativePath is None) to find the path
208 to the default Policy file
210 @param productName (string) The name of the product that the default policy is installed as part of
211 @param fileName (string) The name of the policy file. Can also include a path to the file relative to
212 the directory where the product is installed.
213 @param relativePath (string) The relative path from the directior where the product is installed to
214 the location where the file (or the path to the file) is found. If None
215 (default), the fileName argument is relative to the installation
220 raise RuntimeError(
"No product installed for productName: %s" % basePath)
221 if relativePath
is not None:
222 basePath = os.path.join(basePath, relativePath)
223 fullFilePath = os.path.join(basePath, fileName)
227 """Like dict.update, but will add or modify keys in nested dicts, instead of overwriting the nested
230 For example, for the given code:
231 foo = {'a': {'b': 1}}
232 foo.update({'a': {'c': 2}})
234 If foo is a dict, then after the update foo == {'a': {'c': 2}}
235 But if foo is a Policy, then after the update foo == {'a': {'b': 1, 'c': 2}}
238 for k, v
in u.items():
239 if isinstance(d, collections.abc.Mapping):
240 if isinstance(v, collections.abc.Mapping):
241 r = doUpdate(d.get(k, {}), v)
248 doUpdate(self.
data, other)
251 """Like Policy.update, but will add keys & values from other that DO NOT EXIST in self. Keys and
252 values that already exist in self will NOT be overwritten.
257 otherCopy = copy.deepcopy(other)
258 otherCopy.update(self)
259 self.
data = otherCopy.data
261 def names(self, topLevelOnly=False):
262 """Get the dot-delimited name of all the keys in the hierarchy.
263 NOTE: this is different than the built-in method dict.keys, which will return only the first level
267 return list(self.keys())
269 def getKeys(d, keys, base):
272 levelKey = base +
'.' + key
if base
is not None else key
273 keys.append(levelKey)
274 if isinstance(val, collections.abc.Mapping):
275 getKeys(val, keys, levelKey)
277 getKeys(self.
data, keys,
None)
281 """Get a value as an array. May contain one or more elements.
287 if isinstance(val, str):
289 elif not isinstance(val, collections.abc.Container):
297 """Get the value for a parameter name/key. See class notes about dot-delimited access.
300 :return: the value for the given name.
302 warnings.warn_explicit(
"Deprecated. Use []", DeprecationWarning)
306 """Set the value for a parameter name/key. See class notes about dot-delimited access.
311 warnings.warn(
"Deprecated. Use []", DeprecationWarning)
315 """For any keys in other that are not present in self, sets that key and its value into self.
317 :param other: another Policy
320 warnings.warn(
"Deprecated. Use .merge()", DeprecationWarning)
324 """Query if a key exists in this Policy
327 :return: True if the key exists, else false.
329 warnings.warn(
"Deprecated. Use 'key in object'", DeprecationWarning)
333 """Get the string value of a key.
336 :return: the value for key
338 warnings.warn(
"Deprecated. Use []", DeprecationWarning)
339 return str(self[key])
342 """Get the value of a key.
345 :return: the value for key
347 warnings.warn(
"Deprecated. Use []", DeprecationWarning)
348 return bool(self[key])
356 warnings.warn(
"Deprecated. Use []", DeprecationWarning)
360 """Get a value as an array. May contain one or more elements.
365 warnings.warn(
"Deprecated. Use asArray()", DeprecationWarning)
367 if isinstance(val, str):
369 elif not isinstance(val, collections.abc.Container):
374 if isinstance(other, Policy):
376 return self.
data < other
379 if isinstance(other, Policy):
381 return self.
data <= other
384 if isinstance(other, Policy):
386 return self.
data == other
389 if isinstance(other, Policy):
391 return self.
data != other
394 if isinstance(other, Policy):
396 return self.
data > other
399 if isinstance(other, Policy):
401 return self.
data >= other
407 """Writes the policy to a yaml stream.
415 data = copy.copy(self.
data)
416 keys = [
'defects',
'needCalibRegistry',
'levels',
'defaultLevel',
'defaultSubLevels',
'camera',
417 'exposures',
'calibrations',
'datasets']
420 yaml.safe_dump({key: data.pop(key)}, output, default_flow_style=
False)
425 yaml.safe_dump(data, output, default_flow_style=
False)
428 """Writes the policy to a file.
433 with open(path,
'w')
as f: