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