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: