Package: vivodict
vivodictThis package provides a simple implementation of anauto-vivifiedPythondict, i.e., a
dictionary where accessing a missing key doesn’t raise the standardKeyErrorexception but instead implicitly creates and returns an empty auto-vivifieddictunder that key.FeaturesAuto-vivifiedVivoDictclass derived from the standard Pythondictclass (no third-party dependencies).Auto-vivification of arbitrarily-nesteddictobjects.Convenience methods forflatten,replace, andapplyoperations.Free software: MIT licenseDocumentation:https://vivodict.readthedocs.io.MotivationMy primary motivation for developing this package is because it contained a
piece of code I kept copy-pasting like a bloody caveman between projects.My typical use-cases for this code include:Wrap the decoded JSONdictfrom crummy APIs without a schema that just
decide to drop keys for which the values arenullresulting in code with
nestedif "key" in result:. This allowed me to either retrieve the value
if it was there or at least arriving at an emptydictwhich evaluates toFalsewhen mapping their half-formed data to my own data-structures.Create arbitrarily-nested dictionaries of code stats that I can keep organized
as I like while using in the code and then quicklyflattento a Graphite
compatible format prior to posting them to … well Graphite.Basic UsageThis would be the typical Pythondictbehaviour when accessing a missing
key:>>> d = {"a": 1, "b": 2}
>>> d["a"]
1
>>> d["missing"]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-3-d4f58b57b715> in <module>()
----> 1 d["missing"]

KeyError: 'missing'While if we were using aVivoDict, then upon accessing a missing key we
would be provided with an implicitly created emptyVivoDictas such:>>> from vivodict import VivoDict
>>> d = VivoDict.vivify({"a": 1, "b": 2})
>>> d["a"]
1
>>> d["missing"]
{}NoteNote that instantiation above is not performed simply by passing an existingdicttoVivoDictbut instead through thevivifyclass method
which can recursively convert any arbitrarily-nesteddictto aVivoDict.Now, while the above doesn’t seem to offer anything a simpletry-exceptor
aif "key" not in dwouldn’t offer, theVivoDictbecomes useful when
dealing with arbitrarily nested dictionaries where there may be several levels
of missing keys. For example:>>> from vivodict import VivoDict
>>> d = VivoDict({"a": 1, "b": {"c": 2}, "d": {"e": {"f": 3}}})
>>> d["a"]
1
>>> d["b"]["c"]
2
>>> d["d"]["e"]["f"]
3
>>> d["i"]["am"]["missing"]["eh"] = 4
>>> d
{'a': 1,
'b': {'c': 2},
'd': {'e': {'f': 3}},
'i': {'am': {'missing': {'eh': 4}}}}So, as can be seen, having auto-vivification allows one to nest keys and values
to whatever degree.WarningThe primary caveat of the above functionality is thatVivoDictare very
forgiving when it comes to typos which can leads to weird errors. A
mistyped key will simply create a newVivoDictand will allow you to go
down some rabbithole of erroneously typed keys your linter won’t get you out
of.Convenience FunctionsIn addition to the above, a few basic convenience methods have been built into
theVivoDictclass, mostly cause they make my life easier and lazier.flattenAs I mentioned prior one of my typical use-cases forvivodictis using it
to store nested metrics which I then post to Graphite via simple HTTP requests.Graphite, however, bases its structure on.delimited names where anything
preceding a.is considered to be a metric folder with the last token being
the metric itself.Thus, I needed a quick way to flatten a nesteddictinto a Graphite
compatible version.Theflattenmethod does exactly that:>>> d = VivoDict.vivify({"a": 1, "b": {"c": 2}, "d": {"e": {"f": 3}}})
>>> d.flatten()
{'a': 1, 'b.c': 2, 'd.e.f': 3}replaceFollowing the same premise as withflattenI needed to quickly ‘reset’ my
metrics back to 0 between posting cycles.Hence,replacewill replace all ‘leaf’ node values in what is essentially a
tree with a givenvalue:>>> d = VivoDict.vivify({"a": 1, "b": {"c": 2}, "d": {"e": {"f": 3}}})
>>> d.replace(replace_with=0)
>>> d
{'a': 0, 'b': {'c': 0}, 'd': {'e': {'f': 0}}}WarningAs you may have noticed from the above snippet, thereplacemethod
performs anin-placereplacement instead of returning a copy of the
originalVivoDictwith replaced values.Should you need to maintain an original copy I’d suggest you use thecopypackage and itsdeepcopyfunction (cause Python passes by reference) as
such:>>> import copy
>>> original = VivoDict.vivify({"a": 1, "b": {"c": 2}, "d": {"e": {"f": 3}}})
>>> modified = copy.deepcopy(original)
>>> modified.replace(replace_with=0)
>>> original
{'a': 1, 'b': {'c': 2}, 'd': {'e': {'f': 3}}}
>>> modified
{'a': 0, 'b': {'c': 0}, 'd': {'e': {'f': 0}}}applyLastly, I often had to modify all values through a given function, typically
divide them by a number of observation for average metrics which can be easily
done through theapplymethod which can take anycallableas an
argument and replace the original value with its return-value:>>> d = VivoDict.vivify({"a": 1, "b": {"c": 2}, "d": {"e": {"f": 3}}})
>>> def double(value):
>>>     return value * 2
>>> d.apply(double)
>>> d
{'a': 2, 'b': {'c': 4}, 'd': {'e': {'f': 6}}}
>>> d.apply(lambda value: value / 2)
{'a': 1, 'b': {'c': 2}, 'd': {'e': {'f': 3}}}WarningMuch likereplace, theapplymethod replaces valuesin-place.History0.3.1 (2017-07-23)README.rst: Fixed minor formatting typo.0.3.0 (2017-07-23)Cleanup the docos and removed a bunch of the unnecessary stuff.0.2.0 (2017-07-23)Added more unit-tests and improved docstrings.0.1.1 (2017-07-23)Fixed issues with the Python dependencies.0.1.0 (2017-07-23)First release on PyPI.