uberdict 0.4.3
A Python dict that supports attribute-style access as well as hierarchical keys.
Requires Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, <4

========
uberdict
========

.. image:: https://travis-ci.org/eukaryote/uberdict.svg?branch=master
    :target: https://travis-ci.org/eukaryote/uberdict


``uberdict.udict`` is a Python ``dict`` class that supports attribute-style
access and hierarchical keys:

.. code-block:: python

    my_udict.result.status.code  # is equivalent to:
    my_udict['result']['status']['code']

    my_udict.get('result.status.code', default)  # is equivalent to:
    my_udict.get('result', {}).get('status', {}).get('code', default)

Tested under py27, py32, py33, py34, py35, py36, py37, and
pypy (Python2 and Python 3.5 pypy versions).


Key Features
------------

Easy Conversion from/to plain dict
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The `__init__` method signature matches that of the stdlib's `dict`, so it can
be used as a drop-in replacement for `dict`. If you want to create a `udict`
from a plain `dict` as a deep copy that converts every plain `dict` value at
any level to an equivalent `udict`, use the `udict.fromdict` class method
(and for the reverse direction, use the `todict` instance method of a `udict`
instance):

.. code-block:: python

    d = {
        'result': {
            'status': {
                'code': 200,
                'reason': 'OK'
            }
        }
    }

    # shallow udict copy, like plain `dict` (the `result` value is the original `dict`)
    ud = udict(d)

    # deep udict copy that recursively converts `dict` to `udict`:
    ud = udict.fromdict(d)

    # convert back to plain `dict` (recursively)
    d = ud.todict()


Attribute-Style Access
~~~~~~~~~~~~~~~~~~~~~~

The values in a `udict` may be accessed as if they were attributes on the `udict`,
like normal Python objects:

.. code-block:: python

    d = {
        'result': {
            'status': {
                'code': 200,
                'reason': 'OK'
            }
        }
    }
    ud = udict.fromdict(d)

    assert ud.result.status.code == ud['result']['status']['code']

    # setting an attribute on the `udict` instance works like a normal dict insertion
    ud.message = udict(lang='en', body='Hello, World!')

    assert 'message' in ud
    assert ud['message'] == ud.message


The standard Python attr methods (`hasattr`, `getattr`, `setattr`, and
`delattr`) work as expected.

.. code-block:: python

    # hasattr/getattr/setattr/delattr work as expected
    d = udict()
    assert not hasattr(d, 'foo')
    d.foo = 'foo'
    d['bar'] = 'bar'
    assert hasattr(d, 'foo')
    assert hasattr(d, 'bar')
    setattr(d, 'baz', 'bazbaz')
    assert 'baz' in d
    assert d['baz'] == 'bazbaz'
    delattr(d, 'baz')
    assert 'baz' not in d
    del d['foo']  # works too
    assert 'foo' not in d
    assert not hasattr(d, 'foo')


Note: ``getattr`` and related functions don't interpret a `.` in keys in any
special way, so you can always insert a key containing a `.` using `setattr`,
and can retrieve the value for a key containing a `.` by using `getattr`.


.. code-block:: python

    d = {
        'a': {
            'b': 'a->b'
        },
        'a.b': 'a.b'
    }
    ud = udict.fromdict(d)
    setattr(ud, 'a.b', None)  # doesn't touch 'a'
    assert ud['a.b'] is None
    assert ud.a == d['a']
    assert ud.a.b == 'a->b'


Dict-Style Access and Hierarchical Keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Because a `udict` is a `dict`, you can of course access it like a `dict`:

.. code-block:: python

    ud = udict({'foo': 1})
    assert 'foo' in ud
    ud['foo'] = 2
    ud['foo'] += 1
    assert ud.get('bar', 42) == 42
    del ud['foo']

When a `udict` instance contains nested `udict` instances, you can do the
normal `dict` operations with dotted keys that traverse multiple levels
of the hierarchical structure:

.. code-block:: python

    ud = udict.fromdict({
        'result': {
            'status': {
                'code': 200,
                'reason': 'OK'
            }
        }
    })

    assert ud['result.status.reason'] == 'OK'

    # ud['result.status.reason'] would raise a `KeyError` if the `result` had
    # no `status` or the `status` weren't a `dict`.
    # use `get` if you're unsure of existence:
    assert ud.get('result.foo.bar') is None
    assert ud.get('result.foo.bar', 42) == 42

    # dotted keys work as expected for other dict-style operations too:
    ud['result.status.code'] = 400
    assert 'result.status' in ud and 'result.status.reason' in ud
    del ud['result.status.code']


dict-compatible
~~~~~~~~~~~~~~~

Since a `udict` is a `dict`, it behaves like a `dict` even when used with
brittle code that requires a `dict` instance rather than something that
"quacks" like a `dict`. For example, the stdlib's pretty printing module,
`pprint`, generates a pretty, indented representation of a `udict` that is
identical to the one it generates for a plain `dict`, but `pprint` doesn't
use the dict-style representation for non-dicts even if they support all
the `dict` methods and register themselves as a `collections.Mapping`.

The `__init__` method signature matches that of the stdlib's `dict`, so it can
be used as a drop-in replacement for `dict` with no code-changes needed apart
from using `udict` instead of `dict` (assuming a suitable `import`).

The `str` and `repr` are identical as for a plain `dict` also, and a `udict`
is `==` to an "equivalent" `dict`


Notes
-----

Avoiding Ambiguity of Dotted Keys
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Consider the following `udict`:

.. code-block:: python

    ud = udict.fromdict({
        'a': {
            'b': 'a->b'
        },
        'a.b': 'a.b'
    })

When doing `ud['a.b']`, you might reasonably expect that to evaluate to
`'a.b'`, because there is a top-level `'a.b'` key. But it would
also be reasonable to expect `ud['a.b']` to evaluate to `'a->b'`, since
a dotted key is interpreted as a key that traverses a path from the base `udict`
through a sequence of one more child `dict` values, as described above.

In order to avoid such ambiguities, dict-style access like `ud['a.b']` or
`ud.get('a.b')` is *always* interpreted as if it were `ud['a']['b']` or
`ud.get('a', {}).get('b')`, respectively. That means you could never access the
top-level `'a.b'` in the `udict` above using dict-style access. You'll either
get the value of a nested `udict`, get a `KeyError` (or default value in
case of `udict.get`), or get a `TypeError` in some cases (following normal
Python dict behavior). To access the top-level `'a.b'` mapping,
use `getattr(ud, 'a.b')` instead.  The attribute-style accessors (`hasattr`,
`getattr`, `setattr`, and `delattr`) *always* interpret a key literally, with
no special treatment of keys that contain dots.

Thus, the simple rule to remember is::

    dict-style access with a dotted key is *always* interpreted hierarchically,
    and attribute-style access is *always* interpreted non-hierarchically.


Reasoning about udict Operations
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The following table shows how accessing a value on a `udict` corresponds
to one or more operations on a plain `dict` that yield the same result.

.. table::

   =======================    =============================
     udict operation          dict operation(s)
   =======================    =============================
   ud['a']                    d['a']
   ud.get('a')                d.get('a')
   ud.get('a', 42)            d.get('a', 42)
   ud.a                       d['a']
   getattr(ud, 'a')           d['a']
   getattr(ud, 'a', 42)       d.get('a', 42)
   ud['a.b']                  d['a']['b']
   ud.get('a.b')              d.get('a', {}).get('b')
   ud.get('a.b', 42)          d.get('a', {}).get('b', 42)
   getattr(ud, 'a.b')         d['a.b']
   getattr(ud, 'a.b', 42)     d.get('a.b', 42)
   ud.a.b                     d['a']['b']
   =======================    =============================


The only significant difference between operations on the left-side and those
on the right-side above is when an exception is raised due to there being no
suitable mapping (and no default as there might be with `get` and `getattr`).
In such cases, attribute-style access on a `udict` yields an `AttributeError`
(matching standard Python behavior for attribute access), whereas the
equivalent operation on a `dict` would yield a `KeyError`.


Changes
=======

Version 0.4.3 (2017-07-23)
--------------------------

 * doc changes to get description formatted correctly on both github and pypi


Version 0.4.0 (2017-07-23)
--------------------------

 * support python 2.7 and 3.4+, as well as pypy (pypy2 and dev 3.5 pypy)
 * 100% test coverage
 * making available on pypi


Version 0.3.0 (2014-08-09)
--------------------------

 * added support for `dir` method to improve interactive use (exposes stored keys as well as the normal instance and class attributes that would be expected)
 * updates to ensure that `__missing__` is only used from `__getitem__`, and never from methods like `get` or by inadvertently using `__getitem__` from another method
 * more tests


Version 0.2.0 (2014-07-27)
--------------------------

 * main class is now 'uberdict.udict' (was 'uberdict.UberDict')
 * changes to how dotted keys are handled (dots have no special meaning for 'getattr', 'setattr', 'hasattr', 'delattr' but do for 'get' and '__getitem__' and friends)
 * improved README docs and examples
 * more tests