Source code for waterbutler.core.metrics

import copy


def _merge_dicts(a, b, path=None):
    """"merges b into a

    Taken from: http://stackoverflow.com/a/7205107
    """
    if path is None:
        path = []
    for key in b:
        if key in a:
            if isinstance(a[key], dict) and isinstance(b[key], dict):
                _merge_dicts(a[key], b[key], path + [str(key)])
            elif a[key] == b[key]:
                pass  # same leaf value
            else:
                raise Exception('Conflict at %s' % '.'.join(path + [str(key)]))
        else:
            a[key] = b[key]

    return a


[docs]class MetricsBase(): """Lightweight wrapper around a dict to make keeping track of metrics a little easier. Current functionality is limited, but may be extended later. To do: * update/override method to indicate expectations of existing key self.metrics.add_default('some.flag', True) <later> self.metrics.override('some.flag', False) # dies if 'some.flag' doesn't already exist * optional type validation? self.metrics.add('some.flag', True, bool()) -or- self.metrics.define('some.flag', bool()) <later> self.metrics.add('some.flag', 'foobar') # dies, 'foobar' isn't a bool """ def __init__(self): self._metrics = {}
[docs] def key(self): """ID string for this object""" raise NotImplementedError
[docs] def add(self, key, value): """add() stores the given value under the given key. Subkeys can be specified by placing a dot between the parent and child keys. e.g. 'foo.bar' will be interpreted as ``self._metrics['foo']['bar']`` :param str key: the key to store ``value`` under :param value: the value to store, type unrestricted """ self._set_dotted_key(self._metrics, key, value)
[docs] def incr(self, key): """incr() increments the value stored in key, or initializes it to 1 if it has not yet been set. :param str key: the key to increment the ``value`` of """ value = self._get_dotted_key(self._metrics, key) self._set_dotted_key(self._metrics, key, 1 if value is None else value + 1)
[docs] def append(self, key, new_value): """Assume key points to a list and append ``new_value`` to it. Will initialize a list if key is undefined. Type homogeneity of list members is not enforced. :param str key: the key to store ``value`` under :param value: the value to store, type unrestricted """ old_value = self._get_dotted_key(self._metrics, key) self._set_dotted_key(self._metrics, key, ([] if old_value is None else old_value) + [new_value])
[docs] def merge(self, record): """Merges a dict into the current metrics. :param dict record: a dict to merge with the current metrics """ _merge_dicts(self._metrics, record)
[docs] def serialize(self): """Return a copy of the metrics""" return copy.deepcopy(self._metrics)
[docs] def manifesto(self): """'This is who I am and this is what I stand for!' Returns a dict with one entry: our key pointing to our metrics """ return {self.key: self.serialize()}
def _get_dotted_key(self, store, key): """Naive method to get a nested dict values via dot-separated keys. e.g ``_get_dotted_keys(self._metrics, 'foo.bar')`` is equivalent to ``return self._metrics['foo']['bar']``, but will autovivify any non-existing intermediate dicts and will return ``None`` if the final part does not exist. This method is neither resilient nor intelligent and will react with bad grace if one of the intermediate keys already exists and is not a dict key. """ parts = key.split('.') current = store for part in parts[:-1]: if part not in current: current[part] = {} current = current[part] return current.get(parts[-1], None) def _set_dotted_key(self, store, key, value): """Naive method to set nested dict values via dot-separated keys. e.g ``_set_dotted_keys(self._metrics, 'foo.bar', 'moo')`` is equivalent to ``self._metrics['foo']['bar'] = 'moo'``. This method is neither resilient nor intelligent and will react with bad grace if one of the keys already exists and is not a dict key. """ parts = key.split('.') current = store for part in parts[:-1]: if part not in current: current[part] = {} current = current[part] current[parts[-1]] = value
[docs]class MetricsRecord(MetricsBase): """An extension to MetricsBase that carries a category and list of submetrics. When serialized, will include the serialized child metrics """ def __init__(self, category): super().__init__() self.category = category self.subrecords = [] @property def key(self): """ID string for this record: '{category}'""" return self.category
[docs] def serialize(self): """Returns its metrics with the metrics for each of the subrecords included under their key. """ metrics = super().serialize() for subrecord in self.subrecords: metrics[subrecord.key] = subrecord.serialize() return metrics
[docs] def new_subrecord(self, name): """Create a new MetricsSubRecord object with our category and save it to the subrecords list.""" subrecord = MetricsSubRecord(self.category, name) self.subrecords.append(subrecord) return subrecord
[docs]class MetricsSubRecord(MetricsRecord): """An extension to MetricsRecord that carries a name in addition to a category. Will identify itself as {category}_{name}. Can create its own subrecord whose category will be this subrecord's ``name``. """ def __init__(self, category, name): super().__init__(category) self.name = name @property def key(self): """ID string for this subrecord: '{category}_{name}'""" return '{}_{}'.format(self.category, self.name)
[docs] def new_subrecord(self, name): """Creates and saves a new subrecord. The new subrecord will have its category set to the parent subrecord's ``name``. ex:: parent = MetricsRecord('foo') child = parent.new_subrecord('bar') grandchild = child.new_subrecord('baz') print(parent.key) # foo print(child.key) # foo_bar print(grandchild.key) # bar_baz """ subrecord = MetricsSubRecord(self.name, name) self.subrecords.append(subrecord) return subrecord