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 list(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_l
# use dotty_l if you need support for lists
# WARNING!
# Right now you can read and delete from multidimensional lists
# but setting value is supported only for one dimensional list.
dot = dotty_l({
'annotations': [
{'label': 'app', 'value': 'webapi'},
{'label': 'role', 'value': 'admin'},
]
})
assert dot['annotations.0.label'] == 'app'
assert dot['annotations.0.value'] == 'webapi'
assert dot['annotations.1.label'] == 'role'
assert dot['annotations.1.value'] == 'admin'
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'