Skip to main content

Auto-vivified arbitrarily-nested dictionary (`dict`) classes.

Project description

vivodict

https://img.shields.io/pypi/v/vivodict.svg https://img.shields.io/travis/somada141/vivodict.svg Documentation Status

This package provides a simple implementation of an auto-vivified Python dict, i.e., a dictionary where accessing a missing key doesn’t raise the standard KeyError exception but instead implicitly creates and returns an empty auto-vivified dict under that key.

Features

  • Auto-vivified VivoDict class derived from the standard Python dict class (no third-party dependencies).

  • Auto-vivification of arbitrarily-nested dict objects.

  • Convenience methods for flatten, replace, and apply operations.

  • Free software: MIT license

  • Documentation: https://vivodict.readthedocs.io.

Motivation

My 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 JSON dict from crummy APIs without a schema that just decide to drop keys for which the values are null resulting in code with nested if "key" in result:. This allowed me to either retrieve the value if it was there or at least arriving at an empty dict which evaluates to False when 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 quickly flatten to a Graphite compatible format prior to posting them to … well Graphite.

Basic Usage

This would be the typical Python dict behaviour 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 a VivoDict, then upon accessing a missing key we would be provided with an implicitly created empty VivoDict as such:

>>> from vivodict import VivoDict
>>> d = VivoDict.vivify({"a": 1, "b": 2})
>>> d["a"]
1
>>> d["missing"]
{}

Now, while the above doesn’t seem to offer anything a simple try-except or a if "key" not in d wouldn’t offer, the VivoDict becomes 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.

Convenience Functions

In addition to the above, a few basic convenience methods have been built into the VivoDict class, mostly cause they make my life easier and lazier.

flatten

As I mentioned prior one of my typical use-cases for vivodict is 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 nested dict into a Graphite compatible version.

The flatten method 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}

replace

Following the same premise as with flatten I needed to quickly ‘reset’ my metrics back to 0 between posting cycles.

Hence, replace will replace all ‘leaf’ node values in what is essentially a tree with a given value:

>>> 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}}}

Should you need to maintain an original copy I’d suggest you use the copy package and its deepcopy function (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}}}

apply

Lastly, 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 the apply method which can take any callable as 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}}}

History

0.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.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

vivodict-0.3.1.tar.gz (14.8 kB view details)

Uploaded Source

Built Distribution

vivodict-0.3.1-py2.py3-none-any.whl (10.3 kB view details)

Uploaded Python 2 Python 3

File details

Details for the file vivodict-0.3.1.tar.gz.

File metadata

  • Download URL: vivodict-0.3.1.tar.gz
  • Upload date:
  • Size: 14.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No

File hashes

Hashes for vivodict-0.3.1.tar.gz
Algorithm Hash digest
SHA256 1e4c0d2001bb424e514cab14ea56d31a4d770958973bdf7366ff90dfb9bc81a1
MD5 9725aeba393f8e19b0bff49fa4faa688
BLAKE2b-256 3af52233f19ff549273613c0d7a12947e8d73739007fb2dcf90625e78686ab41

See more details on using hashes here.

File details

Details for the file vivodict-0.3.1-py2.py3-none-any.whl.

File metadata

File hashes

Hashes for vivodict-0.3.1-py2.py3-none-any.whl
Algorithm Hash digest
SHA256 4bab31869bdcf005a7e5258d71109e48940703d71af3fd7d02eedda828e05c5c
MD5 83cb9cb5506bbc2c27c64133294d909b
BLAKE2b-256 670257ade8fd3d8984a3cee6c02adae4a0c287cdf1445a69684f3c3c73be4076

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page