Package: uberdict
uberdict.udictis a Pythondictclass that supports attribute-style
access and hierarchical keys: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 FeaturesEasy Conversion from/to plain dictThe__init__method signature matches that of the stdlib’sdict, so it can
be used as a drop-in replacement fordict. If you want to create audictfrom a plaindictas a deep copy that converts every plaindictvalue at
any level to an equivalentudict, use theudict.fromdictclass method
(and for the reverse direction, use thetodictinstance method of audictinstance):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 AccessThe values in audictmay be accessed as if they were attributes on theudict,
like normal Python objects:d={'result':{'status':{'code':200,'reason':'OK'}}}ud=udict.fromdict(d)assertud.result.status.code==ud['result']['status']['code']# setting an attribute on the `udict` instance works like a normal dict insertionud.message=udict(lang='en',body='Hello, World!')assert'message'inudassertud['message']==ud.messageThe standard Python attr methods (hasattr,getattr,setattr, anddelattr) work as expected.# hasattr/getattr/setattr/delattr work as expectedd=udict()assertnothasattr(d,'foo')d.foo='foo'd['bar']='bar'asserthasattr(d,'foo')asserthasattr(d,'bar')setattr(d,'baz','bazbaz')assert'baz'indassertd['baz']=='bazbaz'delattr(d,'baz')assert'baz'notinddeld['foo']# works tooassert'foo'notindassertnothasattr(d,'foo')Note:getattrand related functions don’t interpret a.in keys in any
special way, so you can always insert a key containing a.usingsetattr,
and can retrieve the value for a key containing a.by usinggetattr.d={'a':{'b':'a->b'},'a.b':'a.b'}ud=udict.fromdict(d)setattr(ud,'a.b',None)# doesn't touch 'a'assertud['a.b']isNoneassertud.a==d['a']assertud.a.b=='a->b'Dict-Style Access and Hierarchical KeysBecause audictis adict, you can of course access it like adict:ud=udict({'foo':1})assert'foo'inudud['foo']=2ud['foo']+=1assertud.get('bar',42)==42delud['foo']When audictinstance contains nestedudictinstances, you can do the
normaldictoperations with dotted keys that traverse multiple levels
of the hierarchical structure:ud=udict.fromdict({'result':{'status':{'code':200,'reason':'OK'}}})assertud['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:assertud.get('result.foo.bar')isNoneassertud.get('result.foo.bar',42)==42# dotted keys work as expected for other dict-style operations too:ud['result.status.code']=400assert'result.status'inudand'result.status.reason'inuddelud['result.status.code']dict-compatibleSince audictis adict, it behaves like adicteven when used with
brittle code that requires adictinstance rather than something that
“quacks” like adict. For example, the stdlib’s pretty printing module,pprint, generates a pretty, indented representation of audictthat is
identical to the one it generates for a plaindict, butpprintdoesn’t
use the dict-style representation for non-dicts even if they support all
thedictmethods and register themselves as acollections.Mapping.The__init__method signature matches that of the stdlib’sdict, so it can
be used as a drop-in replacement fordictwith no code-changes needed apart
from usingudictinstead ofdict(assuming a suitableimport).Thestrandreprare identical as for a plaindictalso, and audictis==to an “equivalent”dictNotesAvoiding Ambiguity of Dotted KeysConsider the followingudict:ud=udict.fromdict({'a':{'b':'a->b'},'a.b':'a.b'})When doingud[‘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 expectud[‘a.b’]to evaluate to‘a->b’, since
a dotted key is interpreted as a key that traverses a path from the baseudictthrough a sequence of one more childdictvalues, as described above.In order to avoid such ambiguities, dict-style access likeud[‘a.b’]orud.get(‘a.b’)isalwaysinterpreted as if it wereud[‘a’][‘b’]orud.get(‘a’, {}).get(‘b’), respectively. That means you could never access the
top-level‘a.b’in theudictabove using dict-style access. You’ll either
get the value of a nestedudict, get aKeyError(or default value in
case ofudict.get), or get aTypeErrorin some cases (following normal
Python dict behavior). To access the top-level‘a.b’mapping,
usegetattr(ud, ‘a.b’)instead.  The attribute-style accessors (hasattr,getattr,setattr, anddelattr)alwaysinterpret 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 OperationsThe following table shows how accessing a value on audictcorresponds
to one or more operations on a plaindictthat yield the same result.udict operationdict operation(s)ud[‘a’]d[‘a’]ud.get(‘a’)d.get(‘a’)ud.get(‘a’, 42)d.get(‘a’, 42)ud.ad[‘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.bd[‘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 withgetandgetattr).
In such cases, attribute-style access on audictyields anAttributeError(matching standard Python behavior for attribute access), whereas the
equivalent operation on adictwould yield aKeyError.ChangesVersion 0.4.3 (2017-07-23)doc changes to get description formatted correctly on both github and pypiVersion 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 coveragemaking available on pypiVersion 0.3.0 (2014-08-09)added support fordirmethod 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 likegetor by inadvertently using__getitem__from another methodmore testsVersion 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 examplesmore tests