Examples

Yes, I know it’s dangerous to follow code examples. Usually examples aren’t in sync with real source code.

But I found a solution … I hope!

Note

All examples are derived from real code hooked to Pytest.
Every change in source code enforce change in examples.
Outdated examples == failed build.

See also

Look at Public API for more details.

Basics

The easiest way to use Dotty dict is with function factory. Factory takes only one, optional dictionary as argument.

If leaved empty, factory function will create new, empty dictionary.

Wrap existing dict

from dotty_dict import dotty

data = {'status': 'ok', 'code': 200, 'data': {'timestamp': 1525018224,
                                              'payload': []}}
data = dotty(data)
assert data['data.timestamp'] == 1525018224

Create new dotty

from dotty_dict import dotty

data = dotty()
data['status'] = 'ok'
data['data.timestamp'] = 1525018224
data['data.fancy.deeply.nested.key.for'] = 'fun'

assert data == {'status': 'ok',
                'data': {
                    'timestamp': 1525018224,
                    'fancy': {
                        'deeply': {
                            'nested': {
                                'key': {
                                    'for': 'fun',
                                },
                            },
                        },
                    },
                }}

Builtin methods

Dotty exposes all native to dict, builtin methods. Only change is made to method which uses key as input to accept dot notation.

from dotty_dict import dotty

dot = dotty({'status': 'ok',
             'data': {
                 'timestamp': 1525018224,
                 'fancy': {
                     'deeply': {
                         'nested': {
                             'key': {
                                 'for': 'fun',
                             },
                         },
                     },
                 },
             }})

# get value, return None if not exist
assert dot.get('data.payload') is None

# pop key
assert dot.pop('data.fancy.deeply.nested.key') == {'for': 'fun'}

# get value and set new value if not exist
assert dot.setdefault('data.payload', []) == []
assert 'payload' in dot['data']

# check what changed
assert dot == {'status': 'ok',
               'data': {
                   'timestamp': 1525018224,
                   'fancy': {
                       'deeply': {
                           'nested': {},
                       },
                   },
                   'payload': [],
               }}

# get keys
assert sorted(dot.keys()) == ['data', 'status']

Advanced

Lets simulate more real scenario. API requests and responses are often very complex with many deeply nested keys. And when you need to check one of them it may looks like: res.get('data', {}).get('service', {}).get('status', {}).get('current', False).

It’s awful! All this empty dictionary fallback to dig in for current status!

Make API request

In this scenario we will send post request to create new user with superuser privileges. Below there is example response as dictionary, and then the way to check granted privileges.

def make_request(payload):
    """Fake request for example purpose.

    :param dict payload: Example payload
    :return dict: Example response
    """
    return {
        'status': {
            'code': 200,
            'msg': 'User created',
        },
        'data': {
            'user': {
                'id': 123,
                'personal': {
                    'name': 'Arnold',
                    'email': 'arnold@dotty.dict',
                },
                'privileges': {
                    'granted': ['login', 'guest', 'superuser'],
                    'denied': ['admin'],
                    'history': {
                        'actions': [
                            ['superuser granted', '2018-04-29T17:08:48'],
                            ['login granted', '2018-04-29T17:08:48'],
                            ['guest granted', '2018-04-29T17:08:48'],
                            ['created', '2018-04-29T17:08:48'],
                            ['signup_submit', '2018-04-29T17:08:47'],
                        ],
                    },
                },
            },
        },
    }

from dotty_dict import dotty

request = dotty()
request['request.data.payload'] = {'name': 'Arnold',
                                   'email': 'arnold@dotty.dict',
                                   'type': 'superuser'}
request['request.data.headers'] = {'content_type': 'application/json'}
request['request.url'] = 'http://127.0.0.1/api/user/create'

response = dotty(make_request(request.to_dict()))

assert response['status.code'] == 200
assert 'superuser' in response['data.user.privileges.granted']

Access dict with embedded lists

This scenario shows how to access subfield in a list.

from dotty_dict import dotty

# dotty supports embedded lists
# WARNING!
# Dotty used to support lists only with dotty_l.
# This feature is depreciated and was removed - now lists have native support.
# If you need old functionality pass additional flag 'no_list' to dotty

dot = dotty({
    'annotations': [
        {'label': 'app', 'value': 'webapi'},
        {'label': 'role', 'value': 'admin'},
    ],
    'spec': {
        'containers': [
            ['gpu', 'tensorflow', 'ML'],
            ['cpu', 'webserver', 'sql'],
        ]
    }
})

assert dot['annotations.0.label'] == 'app'
assert dot['annotations.0.value'] == 'webapi'
assert dot['annotations.1.label'] == 'role'
assert dot['annotations.1.value'] == 'admin'
assert dot['spec.containers.0.0'] == 'gpu'
assert dot['spec.containers.0.1'] == 'tensorflow'
assert dot['spec.containers.0.2'] == 'ML'
assert dot['spec.containers.1.0'] == 'cpu'
assert dot['spec.containers.1.1'] == 'webserver'
assert dot['spec.containers.1.2'] == 'sql'

Access multiple fields with list slices

This scenario shows how to access multiple subfields in a list of dicts.

from dotty_dict import dotty

# dotty supports standard Python slices for lists

dot = dotty({
    'annotations': [
        {'label': 'app', 'value': 'webapi'},
        {'label': 'role', 'value': 'admin'},
        {'label': 'service', 'value': 'mail'},
        {'label': 'database', 'value': 'postgres'}
    ],
})

assert dot['annotations.:.label'] == ['app', 'role', 'service', 'database']
assert dot['annotations.:2.label'] == ['app', 'role']
assert dot['annotations.2:.label'] == ['service', 'database']
assert dot['annotations.::2.label'] == ['app', 'service']

Access numeric fields as dict keys

This scenario shows how to access numeric keys which should not be treated as list indices.

from dotty_dict import dotty

# For special use cases dotty supports dictionary key only access
# With additional flag no_list passed to dotty
# all digits and slices will be treated as string keys

dot = dotty({
    'special': {
        '1': 'one',
        ':': 'colon',
        '2:': 'two colons'
    }
})

assert dot['special.1'] == 'one'
assert dot['special.:'] == 'colon'
assert dot['special.2:'] == 'two colons'

Escape character

In some cases we want to preserve dot in key name and do not treat it as keys separator. It can by done with escape character.

from dotty_dict import dotty

dot = dotty({
    'deep': {
        'key': 'value',
    },
    'key.with.dot': {
        'deeper': 'other value',
    },
})

# how to access deeper value?
assert dot[r'key\.with\.dot.deeper'] == 'other value'

Escape the escape character

What if escape character should be preserved as integral key name, but it happens to be placed right before separator character?

The answer is: Escape the escape character.

Warning

Be careful because backslashes in Python require special treatment.

from dotty_dict import dotty

dot = dotty({
    'deep': {
        'key': 'value',
    },
    'key.with_backslash\\': {  # backslash at the end of key
        'deeper': 'other value',
    },
})

# escape first dot and escape the escape character before second dot
assert dot[r'key\.with_backslash\\.deeper'] == 'other value'

Customization

By default Dotty uses dot as keys separator and backslash as escape character. In special occasions you may want to use different set of chars.

Customization require using Dotty class directly instead of factory function.

Custom separator

In fact any valid string can be used as separator.

from dotty_dict import Dotty

dot = Dotty({'deep': {'deeper': {'harder': 'faster'}}}, separator='$', esc_char='\\')

assert dot['deep$deeper$harder'] == 'faster'

Custom escape char

As separator, escape character can be any valid string not only single character.

from dotty_dict import Dotty

dot = Dotty({'deep.deeper': {'harder': 'faster'}}, separator='.', esc_char='#')

assert dot['deep#.deeper.harder'] == 'faster'